10 Commits

Author SHA1 Message Date
042f4bd330 fix build err 2024-04-04 19:44:56 +08:00
9aef71b09f fix build err 2024-04-04 18:56:48 +08:00
9cbe755520 fix missing elem-type for kritor 2024-04-04 18:51:58 +08:00
df02f9f872 fix #316 2024-03-28 19:37:10 +08:00
5cbb695a66 fix crash 2024-03-28 19:27:11 +08:00
c014e85faa update kritor 2024-03-27 16:21:49 +08:00
4a396b0935 Update kritor 2024-03-25 01:12:59 +08:00
d59fcf9f6a update kritor 2024-03-25 01:10:33 +08:00
cdc664f44a fix build error 2024-03-24 05:33:57 +08:00
ad313f384c fix kritor 2024-03-24 05:19:50 +08:00
45 changed files with 1826 additions and 1744 deletions

4
.gitmodules vendored
View File

@ -1,3 +1,3 @@
[submodule "kritor"] [submodule "kritor"]
path = kritor path = kritor/kritor
url = https://github.com/KarinJS/kritor-kotlin url = https://github.com/KarinJS/kritor

View File

@ -1,7 +1,5 @@
package kritor.service package kritor.service
import kotlin.reflect.KClass
@Retention(AnnotationRetention.SOURCE) @Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.FUNCTION) @Target(AnnotationTarget.FUNCTION)
annotation class Grpc( annotation class Grpc(

View File

@ -1,8 +0,0 @@
package moe.fuqiuluo.symbols
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.CLASS)
annotation class OneBotHandler(
val actionName: String,
val alias: Array<String> = []
)

View File

@ -1,5 +1,5 @@
plugins { plugins {
kotlin("jvm") version "1.9.21" kotlin("jvm") version "1.9.22"
} }
repositories { repositories {

View File

@ -15,8 +15,9 @@ fun ktor(target: String, name: String): String {
return "io.ktor:ktor-$target-$name:${Versions.ktorVersion}" return "io.ktor:ktor-$target-$name:${Versions.ktorVersion}"
} }
fun grpc(name: String, version: String) = "io.grpc:grpc-$name:$version"
object Versions { object Versions {
const val roomVersion = "2.5.0" const val roomVersion = "2.5.0"
const val ktorVersion = "2.3.3" const val ktorVersion = "2.3.3"
} }

1
kritor

Submodule kritor deleted from 10dca646c8

42
kritor/.gitignore vendored Normal file
View File

@ -0,0 +1,42 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

76
kritor/build.gradle.kts Normal file
View File

@ -0,0 +1,76 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
id("com.google.protobuf") version "0.9.4"
}
android {
namespace = "moe.whitechi73.kritor"
compileSdk = 34
defaultConfig {
minSdk = 24
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
protobuf(files("kritor/protos"))
implementation("com.google.protobuf:protobuf-java:4.26.0")
implementation(kotlinx("coroutines-core", "1.8.0"))
implementation(grpc("stub", "1.62.2"))
implementation(grpc("protobuf", "1.62.2"))
implementation(grpc("kotlin-stub", "1.4.1"))
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:4.26.0"
}
plugins {
create("grpc") {
artifact = "io.grpc:protoc-gen-grpc-java:1.62.2"
}
create("grpckt") {
artifact = "io.grpc:protoc-gen-grpc-kotlin:1.4.1:jdk8@jar"
}
}
generateProtoTasks {
all().forEach {
it.plugins {
create("grpc")
create("grpckt")
}
it.builtins {
create("java")
}
}
}
}
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions.freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
}

View File

1
kritor/kritor Submodule

Submodule kritor/kritor added at c49df3074c

21
kritor/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -5,22 +5,9 @@ package moe.fuqiuluo.ksp.impl
import com.google.devtools.ksp.KspExperimental import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.getAnnotationsByType import com.google.devtools.ksp.getAnnotationsByType
import com.google.devtools.ksp.getClassDeclarationByName import com.google.devtools.ksp.processing.*
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.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.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.FileSpec
import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.KModifier
@ -67,15 +54,16 @@ class GrpcProcessor(
} }
funcBuilder.addStatement("return EMPTY_BYTE_ARRAY") funcBuilder.addStatement("return EMPTY_BYTE_ARRAY")
fileSpec fileSpec
.addStatement("import io.kritor.*") .addStatement("import io.kritor.authentication.*")
.addStatement("import io.kritor.core.*") .addStatement("import io.kritor.core.*")
.addStatement("import io.kritor.contact.*") .addStatement("import io.kritor.customization.*")
.addStatement("import io.kritor.group.*") .addStatement("import io.kritor.developer.*")
.addStatement("import io.kritor.friend.*")
.addStatement("import io.kritor.file.*") .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.message.*")
.addStatement("import io.kritor.web.*") .addStatement("import io.kritor.web.*")
.addStatement("import io.kritor.developer.*")
.addFunction(funcBuilder.build()) .addFunction(funcBuilder.build())
.addImport("moe.fuqiuluo.symbols", "EMPTY_BYTE_ARRAY") .addImport("moe.fuqiuluo.symbols", "EMPTY_BYTE_ARRAY")
runCatching { runCatching {

View File

@ -37,7 +37,6 @@ android {
} }
dependencies { dependencies {
//implementation(DEPENDENCY_PROTOBUF)
implementation(kotlinx("serialization-protobuf", "1.6.2")) implementation(kotlinx("serialization-protobuf", "1.6.2"))
implementation(kotlinx("serialization-json", "1.6.2")) implementation(kotlinx("serialization-json", "1.6.2"))

View File

@ -13,7 +13,7 @@ data class ButtonExtra(
@Serializable @Serializable
data class Object1( data class Object1(
@ProtoNumber(1) val rows: List<Row>? = null, @ProtoNumber(1) val rows: List<Row>? = null,
@ProtoNumber(2) val appid: Int? = null, @ProtoNumber(2) val appid: ULong? = null,
) )
@Serializable @Serializable

View File

@ -26,7 +26,7 @@ buildscript {
} }
} }
dependencies { dependencies {
classpath("com.android.tools:r8:8.2.47") classpath("com.android.tools:r8:8.3.37")
} }
} }
@ -34,11 +34,9 @@ rootProject.name = "Shamrock"
include( include(
":app", ":app",
":xposed", ":xposed",
":qqinterface" ":qqinterface",
) ":protobuf",
include(":protobuf") ":processor",
include(":processor") ":annotations",
include(":annotations") ":kritor"
include(":kritor") )
project(":kritor").projectDir = file("kritor/protos")

View File

@ -5,7 +5,6 @@ plugins {
id("org.jetbrains.kotlin.android") id("org.jetbrains.kotlin.android")
id("kotlin-kapt") id("kotlin-kapt")
id("com.google.devtools.ksp") version "1.9.22-1.0.17" id("com.google.devtools.ksp") version "1.9.22-1.0.17"
id("com.google.protobuf") version "0.9.4"
kotlin("plugin.serialization") version "1.9.22" kotlin("plugin.serialization") version "1.9.22"
} }
@ -61,11 +60,10 @@ kotlin {
} }
dependencies { dependencies {
compileOnly ("de.robv.android.xposed:api:82") compileOnly("de.robv.android.xposed:api:82")
compileOnly (project(":qqinterface")) compileOnly(project(":qqinterface"))
protobuf(project(":kritor"))
implementation(project(":kritor"))
implementation(project(":protobuf")) implementation(project(":protobuf"))
implementation(project(":annotations")) implementation(project(":annotations"))
ksp(project(":processor")) ksp(project(":processor"))
@ -75,24 +73,20 @@ dependencies {
DEPENDENCY_ANDROIDX.forEach { DEPENDENCY_ANDROIDX.forEach {
implementation(it) implementation(it)
} }
//implementation(DEPENDENCY_PROTOBUF)
implementation(room("runtime")) implementation(room("runtime"))
kapt(room("compiler")) kapt(room("compiler"))
implementation(room("ktx")) implementation(room("ktx"))
implementation(kotlinx("io-jvm", "0.1.16")) implementation(kotlinx("io-jvm", "0.1.16"))
implementation(kotlinx("serialization-protobuf", "1.6.2"))
implementation(ktor("client", "core")) implementation(ktor("client", "core"))
implementation(ktor("client", "okhttp")) implementation(ktor("client", "okhttp"))
implementation(ktor("serialization", "kotlinx-json")) implementation(ktor("serialization", "kotlinx-json"))
implementation("io.grpc:grpc-stub:1.62.2") implementation(grpc("protobuf", "1.62.2"))
implementation("io.grpc:grpc-protobuf-lite:1.62.2") implementation(grpc("kotlin-stub", "1.4.1"))
implementation("com.google.protobuf:protobuf-kotlin-lite:3.25.3") implementation(grpc("okhttp", "1.62.2"))
implementation("io.grpc:grpc-kotlin-stub:1.4.1")
implementation("io.grpc:grpc-okhttp:1.62.2")
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.ext:junit:1.1.5")
@ -106,40 +100,3 @@ tasks.withType<KotlinCompile>().all {
freeCompilerArgs = listOf("-opt-in=kotlin.RequiresOptIn") freeCompilerArgs = listOf("-opt-in=kotlin.RequiresOptIn")
} }
} }
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.25.3"
}
plugins {
create("java") {
artifact = "io.grpc:protoc-gen-grpc-java:1.62.2"
}
create("grpc") {
artifact = "io.grpc:protoc-gen-grpc-java:1.62.2"
}
create("grpckt") {
artifact = "io.grpc:protoc-gen-grpc-kotlin:1.4.1:jdk8@jar"
}
}
generateProtoTasks {
all().forEach {
it.plugins {
create("java") {
option("lite")
}
create("grpc") {
option("lite")
}
create("grpckt") {
option("lite")
}
}
it.builtins {
create("kotlin") {
option("lite")
}
}
}
}
}

View File

@ -4,14 +4,12 @@ package kritor.client
import com.google.protobuf.ByteString import com.google.protobuf.ByteString
import io.grpc.ManagedChannel import io.grpc.ManagedChannel
import io.grpc.ManagedChannelBuilder import io.grpc.ManagedChannelBuilder
import io.kritor.ReverseServiceGrpcKt import io.kritor.common.Request
import io.kritor.common.Response
import io.kritor.event.EventServiceGrpcKt import io.kritor.event.EventServiceGrpcKt
import io.kritor.event.EventStructure
import io.kritor.event.EventType import io.kritor.event.EventType
import io.kritor.event.eventStructure import io.kritor.reverse.ReverseServiceGrpcKt
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.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
@ -83,23 +81,23 @@ internal class KritorClient(
EventServiceGrpcKt.EventServiceCoroutineStub(channel).registerPassiveListener(channelFlow { EventServiceGrpcKt.EventServiceCoroutineStub(channel).registerPassiveListener(channelFlow {
when(eventType) { when(eventType) {
EventType.EVENT_TYPE_MESSAGE -> GlobalEventTransmitter.onMessageEvent { EventType.EVENT_TYPE_MESSAGE -> GlobalEventTransmitter.onMessageEvent {
send(eventStructure { send(EventStructure.newBuilder().apply {
this.type = EventType.EVENT_TYPE_MESSAGE this.type = EventType.EVENT_TYPE_MESSAGE
this.message = it.second this.message = it.second
}) }.build())
} }
EventType.EVENT_TYPE_CORE_EVENT -> {} EventType.EVENT_TYPE_CORE_EVENT -> {}
EventType.EVENT_TYPE_NOTICE -> GlobalEventTransmitter.onNoticeEvent { EventType.EVENT_TYPE_NOTICE -> GlobalEventTransmitter.onNoticeEvent {
send(eventStructure { send(EventStructure.newBuilder().apply {
this.type = EventType.EVENT_TYPE_NOTICE this.type = EventType.EVENT_TYPE_NOTICE
this.notice = it this.notice = it
}) }.build())
} }
EventType.EVENT_TYPE_REQUEST -> GlobalEventTransmitter.onRequestEvent { EventType.EVENT_TYPE_REQUEST -> GlobalEventTransmitter.onRequestEvent {
send(eventStructure { send(EventStructure.newBuilder().apply {
this.type = EventType.EVENT_TYPE_REQUEST this.type = EventType.EVENT_TYPE_REQUEST
this.request = it this.request = it
}) }.build())
} }
EventType.UNRECOGNIZED -> {} EventType.UNRECOGNIZED -> {}
} }
@ -116,7 +114,7 @@ internal class KritorClient(
val rsp = handleGrpc(request.cmd, request.buf.toByteArray()) val rsp = handleGrpc(request.cmd, request.buf.toByteArray())
senderChannel.emit(Response.newBuilder() senderChannel.emit(Response.newBuilder()
.setCmd(request.cmd) .setCmd(request.cmd)
.setCode(ReqCode.SUCCESS) .setCode(Response.ResponseCode.SUCCESS)
.setMsg("success") .setMsg("success")
.setSeq(request.seq) .setSeq(request.seq)
.setBuf(ByteString.copyFrom(rsp)) .setBuf(ByteString.copyFrom(rsp))
@ -124,7 +122,7 @@ internal class KritorClient(
}.onFailure { }.onFailure {
senderChannel.emit(Response.newBuilder() senderChannel.emit(Response.newBuilder()
.setCmd(request.cmd) .setCmd(request.cmd)
.setCode(ReqCode.INTERNAL) .setCode(Response.ResponseCode.INTERNAL)
.setMsg(it.stackTraceToString()) .setMsg(it.stackTraceToString())
.setSeq(request.seq) .setSeq(request.seq)
.setBuf(ByteString.EMPTY) .setBuf(ByteString.EMPTY)

View File

@ -18,17 +18,16 @@ class KritorServer(
private val server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) private val server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create())
.executor(Dispatchers.IO.asExecutor()) .executor(Dispatchers.IO.asExecutor())
.intercept(AuthInterceptor) .intercept(AuthInterceptor)
.addService(Authentication) .addService(AuthenticationService)
.addService(ContactService) .addService(CoreService)
.addService(KritorService)
.addService(FriendService) .addService(FriendService)
.addService(GroupService) .addService(GroupService)
.addService(GroupFileService) .addService(GroupFileService)
.addService(MessageService) .addService(MessageService)
.addService(EventService) .addService(EventService)
.addService(ForwardMessageService)
.addService(WebService) .addService(WebService)
.addService(DeveloperService) .addService(DeveloperService)
.addService(QsignService)
.build()!! .build()!!
fun start(block: Boolean = false) { fun start(block: Boolean = false) {

View File

@ -1,66 +0,0 @@
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.AuthenticationGrpcKt
import io.kritor.GetAuthStateReq
import io.kritor.GetAuthStateRsp
import io.kritor.authRsp
import io.kritor.getAuthStateRsp
import kritor.auth.AuthInterceptor
import moe.fuqiuluo.shamrock.config.ActiveTicket
import moe.fuqiuluo.shamrock.config.ShamrockConfig
import qq.service.QQInterfaces
internal object Authentication: AuthenticationGrpcKt.AuthenticationCoroutineImplBase() {
@Grpc("Authentication", "Auth")
override suspend fun auth(request: AuthReq): AuthRsp {
if (QQInterfaces.app.account != request.account) {
return authRsp {
code = AuthCode.NO_ACCOUNT
msg = "No such account"
}
}
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 authRsp {
code = AuthCode.OK
msg = "OK"
}
} else {
break
}
} else if (ticket == request.ticket) {
return authRsp {
code = AuthCode.OK
msg = "OK"
}
}
index++
}
return authRsp {
code = AuthCode.NO_TICKET
msg = "Invalid ticket"
}
}
@Grpc("Authentication", "GetAuthState")
override suspend fun getAuthState(request: GetAuthStateReq): GetAuthStateRsp {
if (request.account != QQInterfaces.app.account) {
throw StatusRuntimeException(Status.CANCELLED.withDescription("No such account"))
}
return getAuthStateRsp {
isRequiredAuth = AuthInterceptor.getAllTicket().isNotEmpty()
}
}
}

View File

@ -0,0 +1,60 @@
package kritor.service
import io.grpc.Status
import io.grpc.StatusRuntimeException
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 AuthenticationService: AuthenticationServiceGrpcKt.AuthenticationServiceCoroutineImplBase() {
@Grpc("AuthenticationService", "Authenticate")
override suspend fun authenticate(request: AuthenticateRequest): AuthenticateResponse {
if (QQInterfaces.app.account != request.account) {
return AuthenticateResponse.newBuilder().apply {
code = AuthenticateResponseCode.NO_ACCOUNT
msg = "No such account"
}.build()
}
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 AuthenticateResponse.newBuilder().apply {
code = AuthenticateResponseCode.OK
msg = "OK"
}.build()
} else {
break
}
} else if (ticket == request.ticket) {
return AuthenticateResponse.newBuilder().apply {
code = AuthenticateResponseCode.OK
msg = "OK"
}.build()
}
index++
}
return AuthenticateResponse.newBuilder().apply {
code = AuthenticateResponseCode.NO_TICKET
msg = "Invalid ticket"
}.build()
}
@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 GetAuthenticationStateResponse.newBuilder().apply {
isRequired = AuthInterceptor.getAllTicket().isNotEmpty()
}.build()
}
}

View File

@ -1,183 +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 io.kritor.contact.profileCard
import io.kritor.contact.strangerInfo
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
import kotlin.coroutines.suspendCoroutine
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 { }
}
@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 {
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
}
}
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 {
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()
.setBigVip(info.bBigClubVipOpen == 1.toByte())
.setHollywoodVip(info.bHollywoodVipOpen == 1.toByte())
.setQqVip(info.bQQVipOpen == 1.toByte())
.setSuperVip(info.bSuperQQOpen == 1.toByte())
.setVoted(info.bVoted == 1.toByte())
.build().toByteString()
}
}
@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()
}
}

View File

@ -4,64 +4,35 @@ import android.util.Base64
import com.tencent.mobileqq.app.QQAppInterface import com.tencent.mobileqq.app.QQAppInterface
import io.grpc.Status import io.grpc.Status
import io.grpc.StatusRuntimeException import io.grpc.StatusRuntimeException
import io.kritor.core.ClearCacheRequest import io.kritor.core.*
import io.kritor.core.ClearCacheResponse
import io.kritor.core.DownloadFileRequest
import io.kritor.core.DownloadFileResponse
import io.kritor.core.GetCurrentAccountRequest
import io.kritor.core.GetCurrentAccountResponse
import io.kritor.core.GetDeviceBatteryRequest
import io.kritor.core.GetDeviceBatteryResponse
import io.kritor.core.GetVersionRequest
import io.kritor.core.GetVersionResponse
import io.kritor.core.KritorServiceGrpcKt
import io.kritor.core.SwitchAccountRequest
import io.kritor.core.SwitchAccountResponse
import io.kritor.core.clearCacheResponse
import io.kritor.core.downloadFileResponse
import io.kritor.core.getCurrentAccountResponse
import io.kritor.core.getDeviceBatteryResponse
import io.kritor.core.getVersionResponse
import io.kritor.core.switchAccountResponse
import moe.fuqiuluo.shamrock.tools.ShamrockVersion import moe.fuqiuluo.shamrock.tools.ShamrockVersion
import moe.fuqiuluo.shamrock.utils.DownloadUtils import moe.fuqiuluo.shamrock.utils.DownloadUtils
import moe.fuqiuluo.shamrock.utils.FileUtils import moe.fuqiuluo.shamrock.utils.FileUtils
import moe.fuqiuluo.shamrock.utils.MD5 import moe.fuqiuluo.shamrock.utils.MD5
import moe.fuqiuluo.shamrock.utils.MMKVFetcher
import moe.fuqiuluo.shamrock.utils.PlatformUtils
import mqq.app.MobileQQ import mqq.app.MobileQQ
import qq.service.QQInterfaces
import qq.service.QQInterfaces.Companion.app import qq.service.QQInterfaces.Companion.app
import qq.service.contact.ContactHelper import qq.service.contact.ContactHelper
import java.io.File import java.io.File
internal object KritorService: KritorServiceGrpcKt.KritorServiceCoroutineImplBase() { internal object CoreService : CoreServiceGrpcKt.CoreServiceCoroutineImplBase() {
@Grpc("KritorService", "GetVersion") @Grpc("CoreService", "GetVersion")
override suspend fun getVersion(request: GetVersionRequest): GetVersionResponse { override suspend fun getVersion(request: GetVersionRequest): GetVersionResponse {
return getVersionResponse { return GetVersionResponse.newBuilder().apply {
this.version = ShamrockVersion this.version = ShamrockVersion
this.appName = "Shamrock" this.appName = "Shamrock"
} }.build()
} }
@Grpc("KritorService", "ClearCache") @Grpc("CoreService", "GetCurrentAccount")
override suspend fun clearCache(request: ClearCacheRequest): ClearCacheResponse {
FileUtils.clearCache()
MMKVFetcher.mmkvWithId("audio2silk")
.clear()
return clearCacheResponse {}
}
@Grpc("KritorService", "GetCurrentAccount")
override suspend fun getCurrentAccount(request: GetCurrentAccountRequest): GetCurrentAccountResponse { override suspend fun getCurrentAccount(request: GetCurrentAccountRequest): GetCurrentAccountResponse {
return getCurrentAccountResponse { return GetCurrentAccountResponse.newBuilder().apply {
this.accountName = if (app is QQAppInterface) app.currentNickname else "unknown" this.accountName = if (app is QQAppInterface) app.currentNickname else "unknown"
this.accountUid = app.currentUid ?: "" this.accountUid = app.currentUid ?: ""
this.accountUin = (app.currentUin ?: "0").toLong() this.accountUin = (app.currentUin ?: "0").toLong()
} }.build()
} }
@Grpc("KritorService", "DownloadFile") @Grpc("CoreService", "DownloadFile")
override suspend fun downloadFile(request: DownloadFileRequest): DownloadFileResponse { override suspend fun downloadFile(request: DownloadFileRequest): DownloadFileResponse {
val headerMap = mutableMapOf( val headerMap = mutableMapOf(
"User-Agent" to "Shamrock" "User-Agent" to "Shamrock"
@ -80,13 +51,14 @@ internal object KritorService: KritorServiceGrpcKt.KritorServiceCoroutineImplBas
if (request.hasBase64()) { if (request.hasBase64()) {
val bytes = Base64.decode(request.base64, Base64.DEFAULT) val bytes = Base64.decode(request.base64, Base64.DEFAULT)
tmp.writeBytes(bytes) tmp.writeBytes(bytes)
} else if(request.hasUrl()) { } else if (request.hasUrl()) {
if(!DownloadUtils.download( if (!DownloadUtils.download(
urlAdr = request.url, urlAdr = request.url,
dest = tmp, dest = tmp,
headers = headerMap, headers = headerMap,
threadCount = if (request.hasThreadCnt()) request.threadCnt else 3 threadCount = if (request.hasThreadCnt()) request.threadCnt else 3
)) { )
) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("download failed")) throw StatusRuntimeException(Status.INTERNAL.withDescription("download failed"))
} }
} }
@ -100,18 +72,22 @@ internal object KritorService: KritorServiceGrpcKt.KritorServiceCoroutineImplBas
} }
} }
return downloadFileResponse { return DownloadFileResponse.newBuilder().apply {
this.fileMd5 = MD5.genFileMd5Hex(tmp.absolutePath) this.fileMd5 = MD5.genFileMd5Hex(tmp.absolutePath)
this.fileAbsolutePath = tmp.absolutePath this.fileAbsolutePath = tmp.absolutePath
} }.build()
} }
@Grpc("KritorService", "SwitchAccount") @Grpc("CoreService", "SwitchAccount")
override suspend fun switchAccount(request: SwitchAccountRequest): SwitchAccountResponse { override suspend fun switchAccount(request: SwitchAccountRequest): SwitchAccountResponse {
val uin = when(request.accountCase!!) { val uin = when (request.accountCase!!) {
SwitchAccountRequest.AccountCase.ACCOUNT_UID -> ContactHelper.getUinByUidAsync(request.accountUid) SwitchAccountRequest.AccountCase.ACCOUNT_UID -> ContactHelper.getUinByUidAsync(request.accountUid)
SwitchAccountRequest.AccountCase.ACCOUNT_UIN -> request.accountUin.toString() SwitchAccountRequest.AccountCase.ACCOUNT_UIN -> request.accountUin.toString()
SwitchAccountRequest.AccountCase.ACCOUNT_NOT_SET -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("account not found")) SwitchAccountRequest.AccountCase.ACCOUNT_NOT_SET -> throw StatusRuntimeException(
Status.INVALID_ARGUMENT.withDescription(
"account not found"
)
)
} }
val account = MobileQQ.getMobileQQ().allAccounts.firstOrNull { it.uin == uin } val account = MobileQQ.getMobileQQ().allAccounts.firstOrNull { it.uin == uin }
?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("account not found")) ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("account not found"))
@ -120,17 +96,6 @@ internal object KritorService: KritorServiceGrpcKt.KritorServiceCoroutineImplBas
}.onFailure { }.onFailure {
throw StatusRuntimeException(Status.INTERNAL.withCause(it).withDescription("failed to switch account")) throw StatusRuntimeException(Status.INTERNAL.withCause(it).withDescription("failed to switch account"))
} }
return switchAccountResponse { } return SwitchAccountResponse.newBuilder().build()
}
@Grpc("KritorService", "GetDeviceBattery")
override suspend fun getDeviceBattery(request: GetDeviceBatteryRequest): GetDeviceBatteryResponse {
return getDeviceBatteryResponse {
PlatformUtils.getDeviceBattery().let {
this.battery = it.battery
this.scale = it.scale
this.status = it.status
}
}
} }
} }

View File

@ -1,42 +1,35 @@
package kritor.service package kritor.service
import com.google.protobuf.ByteString import com.google.protobuf.ByteString
import com.tencent.mobileqq.fe.FEKit import io.kritor.developer.*
import com.tencent.mobileqq.qsec.qsecdandelionsdk.Dandelion import moe.fuqiuluo.shamrock.utils.FileUtils
import io.kritor.developer.DeveloperServiceGrpcKt import moe.fuqiuluo.shamrock.utils.MMKVFetcher
import io.kritor.developer.EnergyRequest import moe.fuqiuluo.shamrock.utils.PlatformUtils
import io.kritor.developer.EnergyResponse
import io.kritor.developer.SendPacketRequest
import io.kritor.developer.SendPacketResponse
import io.kritor.developer.SignRequest
import io.kritor.developer.SignResponse
import io.kritor.developer.energyResponse
import io.kritor.developer.sendPacketResponse
import io.kritor.developer.signResponse
import qq.service.QQInterfaces import qq.service.QQInterfaces
internal object DeveloperService: DeveloperServiceGrpcKt.DeveloperServiceCoroutineImplBase() { internal object DeveloperService: DeveloperServiceGrpcKt.DeveloperServiceCoroutineImplBase() {
@Grpc("DeveloperService", "Sign") @Grpc("DeveloperService", "ClearCache")
override suspend fun sign(request: SignRequest): SignResponse { override suspend fun clearCache(request: ClearCacheRequest): ClearCacheResponse {
return signResponse { FileUtils.clearCache()
val result = FEKit.getInstance().getSign(request.command, request.buffer.toByteArray(), request.seq, request.uin) MMKVFetcher.mmkvWithId("audio2silk")
this.sign = ByteString.copyFrom(result.sign) .clear()
this.token = ByteString.copyFrom(result.token) return ClearCacheResponse.newBuilder().build()
this.extra = ByteString.copyFrom(result.extra)
}
} }
@Grpc("DeveloperService", "Energy") @Grpc("DeveloperService", "GetDeviceBattery")
override suspend fun energy(request: EnergyRequest): EnergyResponse { override suspend fun getDeviceBattery(request: GetDeviceBatteryRequest): GetDeviceBatteryResponse {
return energyResponse { return GetDeviceBatteryResponse.newBuilder().apply {
this.result = ByteString.copyFrom(Dandelion.getInstance().fly(request.data, request.salt.toByteArray())) PlatformUtils.getDeviceBattery().let {
} this.battery = it.battery
this.scale = it.scale
this.status = it.status
}
}.build()
} }
@Grpc("DeveloperService", "SendPacket") @Grpc("DeveloperService", "SendPacket")
override suspend fun sendPacket(request: SendPacketRequest): SendPacketResponse { override suspend fun sendPacket(request: SendPacketRequest): SendPacketResponse {
return sendPacketResponse { return SendPacketResponse.newBuilder().apply {
val fromServiceMsg = QQInterfaces.sendBufferAW(request.command, request.isProtobuf, request.requestBuffer.toByteArray()) val fromServiceMsg = QQInterfaces.sendBufferAW(request.command, request.isProtobuf, request.requestBuffer.toByteArray())
if (fromServiceMsg?.wupBuffer == null) { if (fromServiceMsg?.wupBuffer == null) {
this.isSuccess = false this.isSuccess = false
@ -44,7 +37,6 @@ internal object DeveloperService: DeveloperServiceGrpcKt.DeveloperServiceCorouti
this.isSuccess = true this.isSuccess = true
this.responseBuffer = ByteString.copyFrom(fromServiceMsg.wupBuffer) this.responseBuffer = ByteString.copyFrom(fromServiceMsg.wupBuffer)
} }
} }.build()
} }
} }

View File

@ -2,39 +2,40 @@ package kritor.service
import io.grpc.Status import io.grpc.Status
import io.grpc.StatusRuntimeException import io.grpc.StatusRuntimeException
import io.kritor.event.EventRequest
import io.kritor.event.EventServiceGrpcKt import io.kritor.event.EventServiceGrpcKt
import io.kritor.event.EventStructure import io.kritor.event.EventStructure
import io.kritor.event.EventType import io.kritor.event.EventType
import io.kritor.event.RequestPushEvent import io.kritor.event.RequestPushEvent
import io.kritor.event.eventStructure
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.channelFlow
import moe.fuqiuluo.shamrock.internals.GlobalEventTransmitter import moe.fuqiuluo.shamrock.internals.GlobalEventTransmitter
internal object EventService: EventServiceGrpcKt.EventServiceCoroutineImplBase() { internal object EventService : EventServiceGrpcKt.EventServiceCoroutineImplBase() {
override fun registerActiveListener(request: RequestPushEvent): Flow<EventStructure> { override fun registerActiveListener(request: RequestPushEvent): Flow<EventStructure> {
return channelFlow { return channelFlow {
when(request.type!!) { when (request.type!!) {
EventType.EVENT_TYPE_CORE_EVENT -> {} EventType.EVENT_TYPE_CORE_EVENT -> {}
EventType.EVENT_TYPE_MESSAGE -> GlobalEventTransmitter.onMessageEvent { EventType.EVENT_TYPE_MESSAGE -> GlobalEventTransmitter.onMessageEvent {
send(eventStructure { send(EventStructure.newBuilder().apply {
this.type = EventType.EVENT_TYPE_MESSAGE this.type = EventType.EVENT_TYPE_MESSAGE
this.message = it.second this.message = it.second
}) }.build())
} }
EventType.EVENT_TYPE_NOTICE -> GlobalEventTransmitter.onRequestEvent { EventType.EVENT_TYPE_NOTICE -> GlobalEventTransmitter.onRequestEvent {
send(eventStructure { send(EventStructure.newBuilder().apply {
this.type = EventType.EVENT_TYPE_NOTICE this.type = EventType.EVENT_TYPE_NOTICE
this.request = it this.request = it
}) }.build())
} }
EventType.EVENT_TYPE_REQUEST -> GlobalEventTransmitter.onNoticeEvent { EventType.EVENT_TYPE_REQUEST -> GlobalEventTransmitter.onNoticeEvent {
send(eventStructure { send(EventStructure.newBuilder().apply {
this.type = EventType.EVENT_TYPE_NOTICE this.type = EventType.EVENT_TYPE_NOTICE
this.notice = it this.notice = it
}) }.build())
} }
EventType.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT) EventType.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT)
} }
} }

View File

@ -1,50 +0,0 @@
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
}
}
}

View File

@ -1,28 +1,33 @@
package kritor.service 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.Status
import io.grpc.StatusRuntimeException import io.grpc.StatusRuntimeException
import io.kritor.friend.FriendServiceGrpcKt import io.kritor.friend.*
import io.kritor.friend.GetFriendListRequest import kotlinx.coroutines.suspendCancellableCoroutine
import io.kritor.friend.GetFriendListResponse import kotlinx.coroutines.withTimeoutOrNull
import io.kritor.friend.friendData import qq.service.QQInterfaces
import io.kritor.friend.friendExt
import io.kritor.friend.getFriendListResponse
import qq.service.contact.ContactHelper import qq.service.contact.ContactHelper
import qq.service.friend.FriendHelper import qq.service.friend.FriendHelper
import kotlin.coroutines.resume
internal object FriendService: FriendServiceGrpcKt.FriendServiceCoroutineImplBase() { internal object FriendService : FriendServiceGrpcKt.FriendServiceCoroutineImplBase() {
@Grpc("FriendService", "GetFriendList") @Grpc("FriendService", "GetFriendList")
override suspend fun getFriendList(request: GetFriendListRequest): GetFriendListResponse { override suspend fun getFriendList(request: GetFriendListRequest): GetFriendListResponse {
val friendList = FriendHelper.getFriendList(if(request.hasRefresh()) request.refresh else false).onFailure { val friendList = FriendHelper.getFriendList(if (request.hasRefresh()) request.refresh else false).onFailure {
throw StatusRuntimeException(Status.INTERNAL throw StatusRuntimeException(
.withDescription(it.stackTraceToString()) Status.INTERNAL
.withDescription(it.stackTraceToString())
) )
}.getOrThrow() }.getOrThrow()
return getFriendListResponse { return GetFriendListResponse.newBuilder().apply {
friendList.forEach { friendList.forEach {
this.friendList.add(friendData { this.addFriendsInfo(FriendInfo.newBuilder().apply {
uin = it.uin.toLong() uin = it.uin.toLong()
uid = ContactHelper.getUidByUinAsync(uin) uid = ContactHelper.getUidByUinAsync(uin)
qid = "" qid = ""
@ -32,10 +37,208 @@ internal object FriendService: FriendServiceGrpcKt.FriendServiceCoroutineImplBas
level = 0 level = 0
gender = it.gender.toInt() gender = it.gender.toInt()
groupId = it.groupid groupId = it.groupid
ext = friendExt {}.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()
}
} }

View File

@ -15,10 +15,9 @@ import qq.service.QQInterfaces
import qq.service.file.GroupFileHelper import qq.service.file.GroupFileHelper
import qq.service.file.GroupFileHelper.getGroupFileSystemInfo import qq.service.file.GroupFileHelper.getGroupFileSystemInfo
import tencent.im.oidb.cmd0x6d6.oidb_0x6d6 import tencent.im.oidb.cmd0x6d6.oidb_0x6d6
import tencent.im.oidb.cmd0x6d8.oidb_0x6d8
import tencent.im.oidb.oidb_sso import tencent.im.oidb.oidb_sso
internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCoroutineImplBase() { internal object GroupFileService : GroupFileServiceGrpcKt.GroupFileServiceCoroutineImplBase() {
@Grpc("GroupFileService", "CreateFolder") @Grpc("GroupFileService", "CreateFolder")
override suspend fun createFolder(request: CreateFolderRequest): CreateFolderResponse { override suspend fun createFolder(request: CreateFolderRequest): CreateFolderResponse {
val data = Oidb0x6d7ReqBody( val data = Oidb0x6d7ReqBody(
@ -42,21 +41,23 @@ internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCorouti
if (rsp.createFolder?.retCode != 0) { if (rsp.createFolder?.retCode != 0) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to create folder: ${rsp.createFolder?.retCode}")) throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to create folder: ${rsp.createFolder?.retCode}"))
} }
return createFolderResponse { return CreateFolderResponse.newBuilder().apply {
this.id = rsp.createFolder?.folderInfo?.folderId ?: "" this.id = rsp.createFolder?.folderInfo?.folderId ?: ""
this.usedSpace = 0 this.usedSpace = 0
} }.build()
} }
@Grpc("GroupFileService", "DeleteFolder") @Grpc("GroupFileService", "DeleteFolder")
override suspend fun deleteFolder(request: DeleteFolderRequest): DeleteFolderResponse { override suspend fun deleteFolder(request: DeleteFolderRequest): DeleteFolderResponse {
val fromServiceMsg = QQInterfaces.sendOidbAW("OidbSvc.0x6d7_1", 1751, 1, Oidb0x6d7ReqBody( val fromServiceMsg = QQInterfaces.sendOidbAW(
deleteFolder = DeleteFolderReq( "OidbSvc.0x6d7_1", 1751, 1, Oidb0x6d7ReqBody(
groupCode = request.groupId.toULong(), deleteFolder = DeleteFolderReq(
appId = 3u, groupCode = request.groupId.toULong(),
folderId = request.folderId appId = 3u,
) folderId = request.folderId
).toByteArray()) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request")) )
).toByteArray()
) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
if (fromServiceMsg.wupBuffer == null) { if (fromServiceMsg.wupBuffer == null) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed")) throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
} }
@ -66,7 +67,7 @@ internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCorouti
if (rsp.deleteFolder?.retCode != 0) { if (rsp.deleteFolder?.retCode != 0) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to delete folder: ${rsp.deleteFolder?.retCode}")) throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to delete folder: ${rsp.deleteFolder?.retCode}"))
} }
return deleteFolderResponse { } return DeleteFolderResponse.newBuilder().build()
} }
@Grpc("GroupFileService", "DeleteFile") @Grpc("GroupFileService", "DeleteFile")
@ -93,19 +94,21 @@ internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCorouti
if (rsp.delete_file_rsp.int32_ret_code.get() != 0) { if (rsp.delete_file_rsp.int32_ret_code.get() != 0) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to delete file: ${rsp.delete_file_rsp.int32_ret_code.get()}")) throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to delete file: ${rsp.delete_file_rsp.int32_ret_code.get()}"))
} }
return deleteFileResponse { } return DeleteFileResponse.newBuilder().build()
} }
@Grpc("GroupFileService", "RenameFolder") @Grpc("GroupFileService", "RenameFolder")
override suspend fun renameFolder(request: RenameFolderRequest): RenameFolderResponse { override suspend fun renameFolder(request: RenameFolderRequest): RenameFolderResponse {
val fromServiceMsg = QQInterfaces.sendOidbAW("OidbSvc.0x6d7_3", 1751, 3, Oidb0x6d7ReqBody( val fromServiceMsg = QQInterfaces.sendOidbAW(
renameFolder = RenameFolderReq( "OidbSvc.0x6d7_3", 1751, 3, Oidb0x6d7ReqBody(
groupCode = request.groupId.toULong(), renameFolder = RenameFolderReq(
appId = 3u, groupCode = request.groupId.toULong(),
folderId = request.folderId, appId = 3u,
folderName = request.name folderId = request.folderId,
) folderName = request.name
).toByteArray()) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request")) )
).toByteArray()
) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
if (fromServiceMsg.wupBuffer == null) { if (fromServiceMsg.wupBuffer == null) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed")) throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
} }
@ -115,7 +118,7 @@ internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCorouti
if (rsp.renameFolder?.retCode != 0) { if (rsp.renameFolder?.retCode != 0) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to rename folder: ${rsp.renameFolder?.retCode}")) throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to rename folder: ${rsp.renameFolder?.retCode}"))
} }
return renameFolderResponse { } return RenameFolderResponse.newBuilder().build()
} }
@Grpc("GroupFileService", "GetFileSystemInfo") @Grpc("GroupFileService", "GetFileSystemInfo")
@ -123,17 +126,11 @@ internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCorouti
return getGroupFileSystemInfo(request.groupId) return getGroupFileSystemInfo(request.groupId)
} }
@Grpc("GroupFileService", "GetRootFiles") @Grpc("GroupFileService", "GetFileList")
override suspend fun getRootFiles(request: GetRootFilesRequest): GetRootFilesResponse { override suspend fun getFileList(request: GetFileListRequest): GetFileListResponse {
return getRootFilesResponse { return if (request.hasFolderId())
val response = GroupFileHelper.getGroupFiles(request.groupId) GroupFileHelper.getGroupFiles(request.groupId, request.folderId)
this.files.addAll(response.filesList) else
this.folders.addAll(response.foldersList) GroupFileHelper.getGroupFiles(request.groupId)
}
}
@Grpc("GroupFileService", "GetFiles")
override suspend fun getFiles(request: GetFilesRequest): GetFilesResponse {
return GroupFileHelper.getGroupFiles(request.groupId, request.folderId)
} }
} }

View File

@ -2,212 +2,186 @@ package kritor.service
import io.grpc.Status import io.grpc.Status
import io.grpc.StatusRuntimeException import io.grpc.StatusRuntimeException
import io.kritor.group.BanMemberRequest import io.kritor.group.*
import io.kritor.group.BanMemberResponse
import io.kritor.group.GetGroupHonorRequest
import io.kritor.group.GetGroupHonorResponse
import io.kritor.group.GetGroupInfoRequest
import io.kritor.group.GetGroupInfoResponse
import io.kritor.group.GetGroupListRequest
import io.kritor.group.GetGroupListResponse
import io.kritor.group.GetGroupMemberInfoRequest
import io.kritor.group.GetGroupMemberInfoResponse
import io.kritor.group.GetGroupMemberListRequest
import io.kritor.group.GetGroupMemberListResponse
import io.kritor.group.GetNotJoinedGroupInfoRequest
import io.kritor.group.GetNotJoinedGroupInfoResponse
import io.kritor.group.GetProhibitedUserListRequest
import io.kritor.group.GetProhibitedUserListResponse
import io.kritor.group.GetRemainCountAtAllRequest
import io.kritor.group.GetRemainCountAtAllResponse
import io.kritor.group.GroupServiceGrpcKt
import io.kritor.group.KickMemberRequest
import io.kritor.group.KickMemberResponse
import io.kritor.group.LeaveGroupRequest
import io.kritor.group.LeaveGroupResponse
import io.kritor.group.ModifyGroupNameRequest
import io.kritor.group.ModifyGroupNameResponse
import io.kritor.group.ModifyGroupRemarkRequest
import io.kritor.group.ModifyGroupRemarkResponse
import io.kritor.group.ModifyMemberCardRequest
import io.kritor.group.ModifyMemberCardResponse
import io.kritor.group.PokeMemberRequest
import io.kritor.group.PokeMemberResponse
import io.kritor.group.SetGroupAdminRequest
import io.kritor.group.SetGroupAdminResponse
import io.kritor.group.SetGroupUniqueTitleRequest
import io.kritor.group.SetGroupUniqueTitleResponse
import io.kritor.group.SetGroupWholeBanRequest
import io.kritor.group.SetGroupWholeBanResponse
import io.kritor.group.banMemberResponse
import io.kritor.group.getGroupHonorResponse
import io.kritor.group.getGroupInfoResponse
import io.kritor.group.getGroupListResponse
import io.kritor.group.getGroupMemberInfoResponse
import io.kritor.group.getGroupMemberListResponse
import io.kritor.group.getNotJoinedGroupInfoResponse
import io.kritor.group.getProhibitedUserListResponse
import io.kritor.group.getRemainCountAtAllResponse
import io.kritor.group.groupHonorInfo
import io.kritor.group.groupMemberInfo
import io.kritor.group.kickMemberResponse
import io.kritor.group.leaveGroupResponse
import io.kritor.group.modifyGroupNameResponse
import io.kritor.group.modifyGroupRemarkResponse
import io.kritor.group.modifyMemberCardResponse
import io.kritor.group.notJoinedGroupInfo
import io.kritor.group.pokeMemberResponse
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.decodeHonor import moe.fuqiuluo.shamrock.helper.TroopHonorHelper.decodeHonor
import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
import qq.service.contact.ContactHelper import qq.service.contact.ContactHelper
import qq.service.group.GroupHelper import qq.service.group.GroupHelper
internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase() { internal object GroupService : GroupServiceGrpcKt.GroupServiceCoroutineImplBase() {
@Grpc("GroupService", "BanMember") @Grpc("GroupService", "BanMember")
override suspend fun banMember(request: BanMemberRequest): BanMemberResponse { override suspend fun banMember(request: BanMemberRequest): BanMemberResponse {
if (!GroupHelper.isAdmin(request.groupId.toString())) { if (!GroupHelper.isAdmin(request.groupId.toString())) {
throw StatusRuntimeException(Status.PERMISSION_DENIED throw StatusRuntimeException(
.withDescription("You are not admin of this group") Status.PERMISSION_DENIED
.withDescription("You are not admin of this group")
) )
} }
GroupHelper.banMember(request.groupId, when(request.targetCase!!) { GroupHelper.banMember(
BanMemberRequest.TargetCase.TARGET_UIN -> request.targetUin request.groupId, when (request.targetCase!!) {
BanMemberRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong() BanMemberRequest.TargetCase.TARGET_UIN -> request.targetUin
else -> throw StatusRuntimeException(Status.INVALID_ARGUMENT BanMemberRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
.withDescription("target not set") else -> throw StatusRuntimeException(
) Status.INVALID_ARGUMENT
}, request.duration) .withDescription("target not set")
)
}, request.duration
)
return banMemberResponse { return BanMemberResponse.newBuilder().apply {
groupId = request.groupId groupId = request.groupId
} }.build()
} }
@Grpc("GroupService", "PokeMember", ) @Grpc("GroupService", "PokeMember")
override suspend fun pokeMember(request: PokeMemberRequest): PokeMemberResponse { override suspend fun pokeMember(request: PokeMemberRequest): PokeMemberResponse {
GroupHelper.pokeMember(request.groupId, when(request.targetCase!!) { GroupHelper.pokeMember(
PokeMemberRequest.TargetCase.TARGET_UIN -> request.targetUin request.groupId, when (request.targetCase!!) {
PokeMemberRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong() PokeMemberRequest.TargetCase.TARGET_UIN -> request.targetUin
else -> throw StatusRuntimeException(Status.INVALID_ARGUMENT PokeMemberRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
.withDescription("target not set") else -> throw StatusRuntimeException(
) Status.INVALID_ARGUMENT
}) .withDescription("target not set")
return pokeMemberResponse { } )
}
)
return PokeMemberResponse.newBuilder().build()
} }
@Grpc("GroupService", "KickMember") @Grpc("GroupService", "KickMember")
override suspend fun kickMember(request: KickMemberRequest): KickMemberResponse { override suspend fun kickMember(request: KickMemberRequest): KickMemberResponse {
if (!GroupHelper.isAdmin(request.groupId.toString())) { if (!GroupHelper.isAdmin(request.groupId.toString())) {
throw StatusRuntimeException(Status.PERMISSION_DENIED throw StatusRuntimeException(
.withDescription("You are not admin of this group") Status.PERMISSION_DENIED
.withDescription("You are not admin of this group")
) )
} }
GroupHelper.kickMember(request.groupId, request.rejectAddRequest, if (request.hasKickReason()) request.kickReason else "", when(request.targetCase!!) { GroupHelper.kickMember(
KickMemberRequest.TargetCase.TARGET_UIN -> request.targetUin request.groupId,
KickMemberRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong() request.rejectAddRequest,
else -> throw StatusRuntimeException(Status.INVALID_ARGUMENT if (request.hasKickReason()) request.kickReason else "",
.withDescription("target not set") when (request.targetCase!!) {
) KickMemberRequest.TargetCase.TARGET_UIN -> request.targetUin
}) KickMemberRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
return kickMemberResponse { } else -> throw StatusRuntimeException(
Status.INVALID_ARGUMENT
.withDescription("target not set")
)
}
)
return KickMemberResponse.newBuilder().build()
} }
@Grpc("GroupService", "LeaveGroup") @Grpc("GroupService", "LeaveGroup")
override suspend fun leaveGroup(request: LeaveGroupRequest): LeaveGroupResponse { override suspend fun leaveGroup(request: LeaveGroupRequest): LeaveGroupResponse {
GroupHelper.resignTroop(request.groupId.toString()) GroupHelper.resignTroop(request.groupId.toString())
return leaveGroupResponse { } return LeaveGroupResponse.newBuilder().build()
} }
@Grpc("GroupService", "ModifyMemberCard") @Grpc("GroupService", "ModifyMemberCard")
override suspend fun modifyMemberCard(request: ModifyMemberCardRequest): ModifyMemberCardResponse { override suspend fun modifyMemberCard(request: ModifyMemberCardRequest): ModifyMemberCardResponse {
if (!GroupHelper.isAdmin(request.groupId.toString())) { if (!GroupHelper.isAdmin(request.groupId.toString())) {
throw StatusRuntimeException(Status.PERMISSION_DENIED throw StatusRuntimeException(
.withDescription("You are not admin of this group") Status.PERMISSION_DENIED
.withDescription("You are not admin of this group")
) )
} }
GroupHelper.modifyGroupMemberCard(request.groupId, when(request.targetCase!!) { GroupHelper.modifyGroupMemberCard(
ModifyMemberCardRequest.TargetCase.TARGET_UIN -> request.targetUin request.groupId, when (request.targetCase!!) {
ModifyMemberCardRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong() ModifyMemberCardRequest.TargetCase.TARGET_UIN -> request.targetUin
else -> throw StatusRuntimeException(Status.INVALID_ARGUMENT ModifyMemberCardRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid)
.withDescription("target not set") .toLong()
)
}, request.card) else -> throw StatusRuntimeException(
return modifyMemberCardResponse { } Status.INVALID_ARGUMENT
.withDescription("target not set")
)
}, request.card
)
return ModifyMemberCardResponse.newBuilder().build()
} }
@Grpc("GroupService", "ModifyGroupName") @Grpc("GroupService", "ModifyGroupName")
override suspend fun modifyGroupName(request: ModifyGroupNameRequest): ModifyGroupNameResponse { override suspend fun modifyGroupName(request: ModifyGroupNameRequest): ModifyGroupNameResponse {
if (!GroupHelper.isAdmin(request.groupId.toString())) { if (!GroupHelper.isAdmin(request.groupId.toString())) {
throw StatusRuntimeException(Status.PERMISSION_DENIED throw StatusRuntimeException(
.withDescription("You are not admin of this group") Status.PERMISSION_DENIED
.withDescription("You are not admin of this group")
) )
} }
GroupHelper.modifyTroopName(request.groupId.toString(), request.groupName) GroupHelper.modifyTroopName(request.groupId.toString(), request.groupName)
return modifyGroupNameResponse { } return ModifyGroupNameResponse.newBuilder().build()
} }
@Grpc("GroupService", "ModifyGroupRemark") @Grpc("GroupService", "ModifyGroupRemark")
override suspend fun modifyGroupRemark(request: ModifyGroupRemarkRequest): ModifyGroupRemarkResponse { override suspend fun modifyGroupRemark(request: ModifyGroupRemarkRequest): ModifyGroupRemarkResponse {
GroupHelper.modifyGroupRemark(request.groupId, request.remark) GroupHelper.modifyGroupRemark(request.groupId, request.remark)
return modifyGroupRemarkResponse { } return ModifyGroupRemarkResponse.newBuilder().build()
} }
@Grpc("GroupService", "SetGroupAdmin") @Grpc("GroupService", "SetGroupAdmin")
override suspend fun setGroupAdmin(request: SetGroupAdminRequest): SetGroupAdminResponse { override suspend fun setGroupAdmin(request: SetGroupAdminRequest): SetGroupAdminResponse {
if (!GroupHelper.isOwner(request.groupId.toString())) { if (!GroupHelper.isOwner(request.groupId.toString())) {
throw StatusRuntimeException(Status.PERMISSION_DENIED throw StatusRuntimeException(
.withDescription("You are not admin of this group") Status.PERMISSION_DENIED
.withDescription("You are not admin of this group")
) )
} }
GroupHelper.setGroupAdmin(request.groupId, when(request.targetCase!!) { GroupHelper.setGroupAdmin(
SetGroupAdminRequest.TargetCase.TARGET_UIN -> request.targetUin request.groupId, when (request.targetCase!!) {
SetGroupAdminRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong() SetGroupAdminRequest.TargetCase.TARGET_UIN -> request.targetUin
else -> throw StatusRuntimeException(Status.INVALID_ARGUMENT SetGroupAdminRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
.withDescription("target not set") else -> throw StatusRuntimeException(
) Status.INVALID_ARGUMENT
}, request.isAdmin) .withDescription("target not set")
)
}, request.isAdmin
)
return setGroupAdminResponse { } return SetGroupAdminResponse.newBuilder().build()
} }
@Grpc("GroupService", "SetGroupUniqueTitle") @Grpc("GroupService", "SetGroupUniqueTitle")
override suspend fun setGroupUniqueTitle(request: SetGroupUniqueTitleRequest): SetGroupUniqueTitleResponse { override suspend fun setGroupUniqueTitle(request: SetGroupUniqueTitleRequest): SetGroupUniqueTitleResponse {
if (!GroupHelper.isAdmin(request.groupId.toString())) { if (!GroupHelper.isAdmin(request.groupId.toString())) {
throw StatusRuntimeException(Status.PERMISSION_DENIED throw StatusRuntimeException(
.withDescription("You are not admin of this group") Status.PERMISSION_DENIED
.withDescription("You are not admin of this group")
) )
} }
GroupHelper.setGroupUniqueTitle(request.groupId.toString(), when(request.targetCase!!) { GroupHelper.setGroupUniqueTitle(
SetGroupUniqueTitleRequest.TargetCase.TARGET_UIN -> request.targetUin request.groupId.toString(), when (request.targetCase!!) {
SetGroupUniqueTitleRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong() SetGroupUniqueTitleRequest.TargetCase.TARGET_UIN -> request.targetUin
else -> throw StatusRuntimeException(Status.INVALID_ARGUMENT SetGroupUniqueTitleRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid)
.withDescription("target not set") .toLong()
)
}.toString(), request.uniqueTitle)
return setGroupUniqueTitleResponse { } else -> throw StatusRuntimeException(
Status.INVALID_ARGUMENT
.withDescription("target not set")
)
}.toString(), request.uniqueTitle
)
return SetGroupUniqueTitleResponse.newBuilder().build()
} }
@Grpc("GroupService", "SetGroupWholeBan") @Grpc("GroupService", "SetGroupWholeBan")
override suspend fun setGroupWholeBan(request: SetGroupWholeBanRequest): SetGroupWholeBanResponse { override suspend fun setGroupWholeBan(request: SetGroupWholeBanRequest): SetGroupWholeBanResponse {
if (!GroupHelper.isAdmin(request.groupId.toString())) { if (!GroupHelper.isAdmin(request.groupId.toString())) {
throw StatusRuntimeException(Status.PERMISSION_DENIED throw StatusRuntimeException(
.withDescription("You are not admin of this group") Status.PERMISSION_DENIED
.withDescription("You are not admin of this group")
) )
} }
GroupHelper.setGroupWholeBan(request.groupId, request.isBan) GroupHelper.setGroupWholeBan(request.groupId, request.isBan)
return setGroupWholeBanResponse { } return SetGroupWholeBanResponse.newBuilder().build()
} }
@Grpc("GroupService", "GetGroupInfo") @Grpc("GroupService", "GetGroupInfo")
@ -215,18 +189,20 @@ internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase()
val groupInfo = GroupHelper.getGroupInfo(request.groupId.toString(), true).onFailure { val groupInfo = GroupHelper.getGroupInfo(request.groupId.toString(), true).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get group info").withCause(it)) throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get group info").withCause(it))
}.getOrThrow() }.getOrThrow()
return getGroupInfoResponse { return GetGroupInfoResponse.newBuilder().apply {
this.groupInfo = io.kritor.group.groupInfo { this.groupInfo = GroupInfo.newBuilder().apply {
groupId = groupInfo.troopcode.toLong() groupId = groupInfo.troopcode.toLong()
groupName = groupInfo.troopname.ifNullOrEmpty { groupInfo.troopRemark }.ifNullOrEmpty { groupInfo.newTroopName } ?: "" groupName =
groupInfo.troopname.ifNullOrEmpty { groupInfo.troopRemark }.ifNullOrEmpty { groupInfo.newTroopName }
?: ""
groupRemark = groupInfo.troopRemark ?: "" groupRemark = groupInfo.troopRemark ?: ""
owner = groupInfo.troopowneruin?.toLong() ?: 0 owner = groupInfo.troopowneruin?.toLong() ?: 0
admins.addAll(GroupHelper.getAdminList(groupId)) addAllAdmins(GroupHelper.getAdminList(groupId))
maxMemberCount = groupInfo.wMemberMax maxMemberCount = groupInfo.wMemberMax
memberCount = groupInfo.wMemberNum memberCount = groupInfo.wMemberNum
groupUin = groupInfo.troopuin?.toLong() ?: 0 groupUin = groupInfo.troopuin?.toLong() ?: 0
} }.build()
} }.build()
} }
@Grpc("GroupService", "GetGroupList") @Grpc("GroupService", "GetGroupList")
@ -234,36 +210,45 @@ internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase()
val groupList = GroupHelper.getGroupList(if (request.hasRefresh()) request.refresh else false).onFailure { val groupList = GroupHelper.getGroupList(if (request.hasRefresh()) request.refresh else false).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get group list").withCause(it)) throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get group list").withCause(it))
}.getOrThrow() }.getOrThrow()
return getGroupListResponse { return GetGroupListResponse.newBuilder().apply {
groupList.forEach { groupInfo -> groupList.forEach { groupInfo ->
this.groupInfo.add(io.kritor.group.groupInfo { this.addGroupsInfo(GroupInfo.newBuilder().apply {
groupId = groupInfo.troopcode.toLong() groupId = groupInfo.troopcode.toLong()
groupName = groupInfo.troopname.ifNullOrEmpty { groupInfo.troopRemark }.ifNullOrEmpty { groupInfo.newTroopName } ?: "" groupName = groupInfo.troopname.ifNullOrEmpty { groupInfo.troopRemark }
.ifNullOrEmpty { groupInfo.newTroopName } ?: ""
groupRemark = groupInfo.troopRemark ?: "" groupRemark = groupInfo.troopRemark ?: ""
owner = groupInfo.troopowneruin?.toLong() ?: 0 owner = groupInfo.troopowneruin?.toLong() ?: 0
admins.addAll(GroupHelper.getAdminList(groupId)) addAllAdmins(GroupHelper.getAdminList(groupId))
maxMemberCount = groupInfo.wMemberMax maxMemberCount = groupInfo.wMemberMax
memberCount = groupInfo.wMemberNum memberCount = groupInfo.wMemberNum
groupUin = groupInfo.troopuin?.toLong() ?: 0 groupUin = groupInfo.troopuin?.toLong() ?: 0
}) })
} }
} }.build()
} }
@Grpc("GroupService", "GetGroupMemberInfo") @Grpc("GroupService", "GetGroupMemberInfo")
override suspend fun getGroupMemberInfo(request: GetGroupMemberInfoRequest): GetGroupMemberInfoResponse { override suspend fun getGroupMemberInfo(request: GetGroupMemberInfoRequest): GetGroupMemberInfoResponse {
val memberInfo = GroupHelper.getTroopMemberInfoByUin(request.groupId.toString(), when(request.targetCase!!) { val memberInfo = GroupHelper.getTroopMemberInfoByUin(
GetGroupMemberInfoRequest.TargetCase.UIN -> request.uin request.groupId.toString(), when (request.targetCase!!) {
GetGroupMemberInfoRequest.TargetCase.UID -> ContactHelper.getUinByUidAsync(request.uid).toLong() GetGroupMemberInfoRequest.TargetCase.TARGET_UID -> request.targetUin
else -> throw StatusRuntimeException(Status.INVALID_ARGUMENT GetGroupMemberInfoRequest.TargetCase.TARGET_UIN -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
.withDescription("target not set") else -> throw StatusRuntimeException(
Status.INVALID_ARGUMENT
.withDescription("target not set")
)
}.toString()
).onFailure {
throw StatusRuntimeException(
Status.INTERNAL.withDescription("unable to get group member info").withCause(it)
) )
}.toString()).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get group member info").withCause(it))
}.getOrThrow() }.getOrThrow()
return getGroupMemberInfoResponse { return GetGroupMemberInfoResponse.newBuilder().apply {
groupMemberInfo = groupMemberInfo { groupMemberInfo = GroupMemberInfo.newBuilder().apply {
uid = if (request.targetCase == GetGroupMemberInfoRequest.TargetCase.UID) request.uid else ContactHelper.getUidByUinAsync(request.uin) uid =
if (request.targetCase == GetGroupMemberInfoRequest.TargetCase.TARGET_UID) request.targetUid else ContactHelper.getUidByUinAsync(
request.targetUin
)
uin = memberInfo.memberuin?.toLong() ?: 0 uin = memberInfo.memberuin?.toLong() ?: 0
nick = memberInfo.troopnick nick = memberInfo.troopnick
.ifNullOrEmpty { memberInfo.hwName } .ifNullOrEmpty { memberInfo.hwName }
@ -279,24 +264,29 @@ internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase()
shutUpTimestamp = memberInfo.gagTimeStamp shutUpTimestamp = memberInfo.gagTimeStamp
distance = memberInfo.distance distance = memberInfo.distance
honor.addAll((memberInfo.honorList ?: "") addAllHonors((memberInfo.honorList ?: "")
.split("|") .split("|")
.filter { it.isNotBlank() } .filter { it.isNotBlank() }
.map { it.toInt() }) .map { it.toInt() })
unfriendly = false unfriendly = false
cardChangeable = GroupHelper.isAdmin(request.groupId.toString()) cardChangeable = GroupHelper.isAdmin(request.groupId.toString())
} }.build()
} }.build()
} }
@Grpc("GroupService", "GetGroupMemberList") @Grpc("GroupService", "GetGroupMemberList")
override suspend fun getGroupMemberList(request: GetGroupMemberListRequest): GetGroupMemberListResponse { override suspend fun getGroupMemberList(request: GetGroupMemberListRequest): GetGroupMemberListResponse {
val memberList = GroupHelper.getGroupMemberList(request.groupId.toString(), if (request.hasRefresh()) request.refresh else false).onFailure { val memberList = GroupHelper.getGroupMemberList(
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get group member list").withCause(it)) request.groupId.toString(),
if (request.hasRefresh()) request.refresh else false
).onFailure {
throw StatusRuntimeException(
Status.INTERNAL.withDescription("unable to get group member list").withCause(it)
)
}.getOrThrow() }.getOrThrow()
return getGroupMemberListResponse { return GetGroupMemberListResponse.newBuilder().apply {
memberList.forEach { memberInfo -> memberList.forEach { memberInfo ->
this.groupMemberInfo.add(groupMemberInfo { this.addGroupMembersInfo(GroupMemberInfo.newBuilder().apply {
uid = ContactHelper.getUidByUinAsync(memberInfo.memberuin?.toLong() ?: 0) uid = ContactHelper.getUidByUinAsync(memberInfo.memberuin?.toLong() ?: 0)
uin = memberInfo.memberuin?.toLong() ?: 0 uin = memberInfo.memberuin?.toLong() ?: 0
nick = memberInfo.troopnick nick = memberInfo.troopnick
@ -313,7 +303,7 @@ internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase()
shutUpTimestamp = memberInfo.gagTimeStamp shutUpTimestamp = memberInfo.gagTimeStamp
distance = memberInfo.distance distance = memberInfo.distance
honor.addAll((memberInfo.honorList ?: "") addAllHonors((memberInfo.honorList ?: "")
.split("|") .split("|")
.filter { it.isNotBlank() } .filter { it.isNotBlank() }
.map { it.toInt() }) .map { it.toInt() })
@ -321,23 +311,25 @@ internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase()
cardChangeable = GroupHelper.isAdmin(request.groupId.toString()) cardChangeable = GroupHelper.isAdmin(request.groupId.toString())
}) })
} }
} }.build()
} }
@Grpc("GroupService", "GetProhibitedUserList") @Grpc("GroupService", "GetProhibitedUserList")
override suspend fun getProhibitedUserList(request: GetProhibitedUserListRequest): GetProhibitedUserListResponse { override suspend fun getProhibitedUserList(request: GetProhibitedUserListRequest): GetProhibitedUserListResponse {
val prohibitedList = GroupHelper.getProhibitedMemberList(request.groupId).onFailure { val prohibitedList = GroupHelper.getProhibitedMemberList(request.groupId).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get prohibited user list").withCause(it)) throw StatusRuntimeException(
Status.INTERNAL.withDescription("unable to get prohibited user list").withCause(it)
)
}.getOrThrow() }.getOrThrow()
return getProhibitedUserListResponse { return GetProhibitedUserListResponse.newBuilder().apply {
prohibitedList.forEach { prohibitedList.forEach {
this.prohibitedUserInfo.add(prohibitedUserInfo { this.addProhibitedUsersInfo(ProhibitedUserInfo.newBuilder().apply {
uid = ContactHelper.getUidByUinAsync(it.memberUin) uid = ContactHelper.getUidByUinAsync(it.memberUin)
uin = it.memberUin uin = it.memberUin
prohibitedTime = it.shutuptimestap prohibitedTime = it.shutuptimestap
}) })
} }
} }.build()
} }
@Grpc("GroupService", "GetRemainCountAtAll") @Grpc("GroupService", "GetRemainCountAtAll")
@ -345,20 +337,22 @@ internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase()
val remainAtAllRsp = GroupHelper.getGroupRemainAtAllRemain(request.groupId).onFailure { val remainAtAllRsp = GroupHelper.getGroupRemainAtAllRemain(request.groupId).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get remain count").withCause(it)) throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get remain count").withCause(it))
}.getOrThrow() }.getOrThrow()
return getRemainCountAtAllResponse { return GetRemainCountAtAllResponse.newBuilder().apply {
accessAtAll = remainAtAllRsp.bool_can_at_all.get() accessAtAll = remainAtAllRsp.bool_can_at_all.get()
remainCountForGroup = remainAtAllRsp.uint32_remain_at_all_count_for_group.get() remainCountForGroup = remainAtAllRsp.uint32_remain_at_all_count_for_group.get()
remainCountForSelf = remainAtAllRsp.uint32_remain_at_all_count_for_uin.get() remainCountForSelf = remainAtAllRsp.uint32_remain_at_all_count_for_uin.get()
} }.build()
} }
@Grpc("GroupService", "GetNotJoinedGroupInfo") @Grpc("GroupService", "GetNotJoinedGroupInfo")
override suspend fun getNotJoinedGroupInfo(request: GetNotJoinedGroupInfoRequest): GetNotJoinedGroupInfoResponse { override suspend fun getNotJoinedGroupInfo(request: GetNotJoinedGroupInfoRequest): GetNotJoinedGroupInfoResponse {
val groupInfo = GroupHelper.getNotJoinedGroupInfo(request.groupId).onFailure { val groupInfo = GroupHelper.getNotJoinedGroupInfo(request.groupId).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get not joined group info").withCause(it)) throw StatusRuntimeException(
Status.INTERNAL.withDescription("unable to get not joined group info").withCause(it)
)
}.getOrThrow() }.getOrThrow()
return getNotJoinedGroupInfoResponse { return GetNotJoinedGroupInfoResponse.newBuilder().apply {
this.groupInfo = notJoinedGroupInfo { this.groupInfo = NotJoinedGroupInfo.newBuilder().apply {
groupId = groupInfo.groupId groupId = groupInfo.groupId
groupName = groupInfo.groupName groupName = groupInfo.groupName
owner = groupInfo.owner owner = groupInfo.owner
@ -368,15 +362,17 @@ internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase()
createTime = groupInfo.createTime.toInt() createTime = groupInfo.createTime.toInt()
groupFlag = groupInfo.groupFlag groupFlag = groupInfo.groupFlag
groupFlagExt = groupInfo.groupFlagExt groupFlagExt = groupInfo.groupFlagExt
} }.build()
} }.build()
} }
@Grpc("GroupService", "GetGroupHonor") @Grpc("GroupService", "GetGroupHonor")
override suspend fun getGroupHonor(request: GetGroupHonorRequest): GetGroupHonorResponse { override suspend fun getGroupHonor(request: GetGroupHonorRequest): GetGroupHonorResponse {
return getGroupHonorResponse { return GetGroupHonorResponse.newBuilder().apply {
GroupHelper.getGroupMemberList(request.groupId.toString(), true).onFailure { GroupHelper.getGroupMemberList(request.groupId.toString(), true).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get group member list").withCause(it)) throw StatusRuntimeException(
Status.INTERNAL.withDescription("unable to get group member list").withCause(it)
)
}.onSuccess { memberList -> }.onSuccess { memberList ->
memberList.forEach { member -> memberList.forEach { member ->
(member.honorList ?: "").split("|") (member.honorList ?: "").split("|")
@ -384,7 +380,7 @@ internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase()
.map { it.toInt() }.forEach { .map { it.toInt() }.forEach {
val honor = decodeHonor(member.memberuin.toLong(), it, member.mHonorRichFlag) val honor = decodeHonor(member.memberuin.toLong(), it, member.mHonorRichFlag)
if (honor != null) { if (honor != null) {
groupHonorInfo.add(groupHonorInfo { addGroupHonorsInfo(GroupHonorInfo.newBuilder().apply {
uid = ContactHelper.getUidByUinAsync(member.memberuin.toLong()) uid = ContactHelper.getUidByUinAsync(member.memberuin.toLong())
uin = member.memberuin.toLong() uin = member.memberuin.toLong()
nick = member.troopnick nick = member.troopnick
@ -400,6 +396,6 @@ internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase()
} }
} }
} }
} }.build()
} }
} }

View File

@ -7,95 +7,55 @@ import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
import com.tencent.qqnt.msg.api.IMsgService import com.tencent.qqnt.msg.api.IMsgService
import io.grpc.Status import io.grpc.Status
import io.grpc.StatusRuntimeException import io.grpc.StatusRuntimeException
import io.kritor.message.ClearMessagesRequest import io.kritor.common.*
import io.kritor.message.ClearMessagesResponse import io.kritor.message.*
import io.kritor.message.DeleteEssenceMsgRequest
import io.kritor.message.DeleteEssenceMsgResponse
import io.kritor.message.GetEssenceMessagesRequest
import io.kritor.message.GetEssenceMessagesResponse
import io.kritor.message.GetForwardMessagesRequest
import io.kritor.message.GetForwardMessagesResponse
import io.kritor.message.GetHistoryMessageRequest
import io.kritor.message.GetHistoryMessageResponse
import io.kritor.message.GetMessageBySeqRequest
import io.kritor.message.GetMessageBySeqResponse
import io.kritor.message.GetMessageRequest
import io.kritor.message.GetMessageResponse
import io.kritor.message.MessageServiceGrpcKt
import io.kritor.message.RecallMessageRequest
import io.kritor.message.RecallMessageResponse
import io.kritor.message.Scene
import io.kritor.message.SendMessageByResIdRequest
import io.kritor.message.SendMessageByResIdResponse
import io.kritor.message.SendMessageRequest
import io.kritor.message.SendMessageResponse
import io.kritor.message.SetEssenceMessageRequest
import io.kritor.message.SetEssenceMessageResponse
import io.kritor.message.SetMessageCommentEmojiRequest
import io.kritor.message.SetMessageCommentEmojiResponse
import io.kritor.message.clearMessagesResponse
import io.kritor.message.contact
import io.kritor.message.deleteEssenceMsgResponse
import io.kritor.message.essenceMessage
import io.kritor.message.getEssenceMessagesResponse
import io.kritor.message.getForwardMessagesResponse
import io.kritor.message.getHistoryMessageResponse
import io.kritor.message.getMessageBySeqResponse
import io.kritor.message.getMessageResponse
import io.kritor.message.messageBody
import io.kritor.message.recallMessageResponse
import io.kritor.message.sendMessageByResIdResponse
import io.kritor.message.sendMessageResponse
import io.kritor.message.sender
import io.kritor.message.setEssenceMessageResponse
import io.kritor.message.setMessageCommentEmojiResponse
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.LogCenter
import protobuf.auto.toByteArray import protobuf.auto.toByteArray
import protobuf.message.ContentHead import protobuf.message.*
import protobuf.message.Elem
import protobuf.message.MsgBody
import protobuf.message.PbSendMsgReq
import protobuf.message.RichText
import protobuf.message.RoutingHead
import protobuf.message.element.GeneralFlags import protobuf.message.element.GeneralFlags
import protobuf.message.routing.C2C import protobuf.message.routing.C2C
import protobuf.message.routing.Grp import protobuf.message.routing.Grp
import qq.service.QQInterfaces import qq.service.QQInterfaces
import qq.service.contact.longPeer import qq.service.contact.longPeer
import qq.service.internals.NTServiceFetcher import qq.service.internals.NTServiceFetcher
import qq.service.msg.*
import qq.service.msg.ForwardMessageHelper
import qq.service.msg.MessageHelper import qq.service.msg.MessageHelper
import qq.service.msg.NtMsgConvertor
import qq.service.msg.toKritorEventMessages
import qq.service.msg.toKritorReqMessages
import qq.service.msg.toKritorResponseMessages
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.random.Random import kotlin.random.Random
import kotlin.random.nextUInt import kotlin.random.nextUInt
internal object MessageService: MessageServiceGrpcKt.MessageServiceCoroutineImplBase() { internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImplBase() {
@Grpc("MessageService", "SendMessage") @Grpc("MessageService", "SendMessage")
override suspend fun sendMessage(request: SendMessageRequest): SendMessageResponse { override suspend fun sendMessage(request: SendMessageRequest): SendMessageResponse {
val contact = request.contact.let { val contact = request.contact.let {
MessageHelper.generateContact(when(it.scene!!) { MessageHelper.generateContact(
Scene.GROUP -> MsgConstant.KCHATTYPEGROUP when (it.scene!!) {
Scene.FRIEND -> MsgConstant.KCHATTYPEC2C Scene.GROUP -> MsgConstant.KCHATTYPEGROUP
Scene.GUILD -> MsgConstant.KCHATTYPEGUILD Scene.FRIEND -> MsgConstant.KCHATTYPEC2C
Scene.STRANGER_FROM_GROUP -> MsgConstant.KCHATTYPETEMPC2CFROMGROUP Scene.GUILD -> MsgConstant.KCHATTYPEGUILD
Scene.NEARBY -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN Scene.STRANGER_FROM_GROUP -> MsgConstant.KCHATTYPETEMPC2CFROMGROUP
Scene.STRANGER -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN Scene.NEARBY -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene")) Scene.STRANGER -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
}, it.peer, it.subPeer) Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene"))
}, it.peer, it.subPeer
)
} }
val uniseq = MessageHelper.generateMsgId(contact.chatType) val uniseq = MessageHelper.generateMsgId(contact.chatType)
return sendMessageResponse { return SendMessageResponse.newBuilder().apply {
this.messageId = MessageHelper.sendMessage(contact, NtMsgConvertor.convertToNtMsgs(contact, uniseq, request.elementsList), request.retryCount, uniseq).onFailure { this.messageId = MessageHelper.sendMessage(
contact,
NtMsgConvertor.convertToNtMsgs(contact, uniseq, request.elementsList),
request.retryCount,
uniseq
).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withCause(it)) throw StatusRuntimeException(Status.INTERNAL.withCause(it))
}.getOrThrow() }.getOrThrow().toString()
} }.build()
} }
@Grpc("MessageService", "SendMessageByResId") @Grpc("MessageService", "SendMessageByResId")
@ -125,16 +85,16 @@ internal object MessageService: MessageServiceGrpcKt.MessageServiceCoroutineImpl
msgVia = 0u msgVia = 0u
) )
QQInterfaces.sendBuffer("MessageSvc.PbSendMsg", true, req.toByteArray()) QQInterfaces.sendBuffer("MessageSvc.PbSendMsg", true, req.toByteArray())
return sendMessageByResIdResponse { } return SendMessageByResIdResponse.newBuilder().build()
} }
@Grpc("MessageService", "ClearMessages") @Grpc("MessageService", "SetMessageReaded")
override suspend fun clearMessages(request: ClearMessagesRequest): ClearMessagesResponse { override suspend fun setMessageReaded(request: SetMessageReadRequest): SetMessageReadResponse {
val contact = request.contact val contact = request.contact
val kernelService = NTServiceFetcher.kernelService val kernelService = NTServiceFetcher.kernelService
val sessionService = kernelService.wrapperSession val sessionService = kernelService.wrapperSession
val service = sessionService.msgService val service = sessionService.msgService
val chatType = when(contact.scene!!) { val chatType = when (contact.scene!!) {
Scene.GROUP -> MsgConstant.KCHATTYPEGROUP Scene.GROUP -> MsgConstant.KCHATTYPEGROUP
Scene.FRIEND -> MsgConstant.KCHATTYPEC2C Scene.FRIEND -> MsgConstant.KCHATTYPEC2C
Scene.GUILD -> MsgConstant.KCHATTYPEGUILD Scene.GUILD -> MsgConstant.KCHATTYPEGUILD
@ -144,92 +104,55 @@ internal object MessageService: MessageServiceGrpcKt.MessageServiceCoroutineImpl
Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene")) Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene"))
} }
service.clearMsgRecords(Contact(chatType, contact.peer, contact.subPeer), null) service.clearMsgRecords(Contact(chatType, contact.peer, contact.subPeer), null)
return clearMessagesResponse { } return SetMessageReadResponse.newBuilder().build()
} }
@Grpc("MessageService", "RecallMessage") @Grpc("MessageService", "RecallMessage")
override suspend fun recallMessage(request: RecallMessageRequest): RecallMessageResponse { override suspend fun recallMessage(request: RecallMessageRequest): RecallMessageResponse {
val contact = request.contact.let { val contact = request.contact.let {
MessageHelper.generateContact(when(it.scene!!) { MessageHelper.generateContact(
Scene.GROUP -> MsgConstant.KCHATTYPEGROUP when (it.scene!!) {
Scene.FRIEND -> MsgConstant.KCHATTYPEC2C Scene.GROUP -> MsgConstant.KCHATTYPEGROUP
Scene.GUILD -> MsgConstant.KCHATTYPEGUILD Scene.FRIEND -> MsgConstant.KCHATTYPEC2C
Scene.STRANGER_FROM_GROUP -> MsgConstant.KCHATTYPETEMPC2CFROMGROUP Scene.GUILD -> MsgConstant.KCHATTYPEGUILD
Scene.NEARBY -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN Scene.STRANGER_FROM_GROUP -> MsgConstant.KCHATTYPETEMPC2CFROMGROUP
Scene.STRANGER -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN Scene.NEARBY -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene")) Scene.STRANGER -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
}, it.peer, it.subPeer) Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene"))
}, it.peer, it.subPeer
)
} }
val kernelService = NTServiceFetcher.kernelService val kernelService = NTServiceFetcher.kernelService
val sessionService = kernelService.wrapperSession val sessionService = kernelService.wrapperSession
val service = sessionService.msgService val service = sessionService.msgService
service.recallMsg(contact, arrayListOf(request.messageId)) { code, msg -> service.recallMsg(contact, arrayListOf(request.messageId.toLong())) { code, msg ->
if (code != 0) { if (code != 0) {
LogCenter.log("消息撤回失败: $code:$msg", Level.WARN) LogCenter.log("消息撤回失败: $code:$msg", Level.WARN)
} }
} }
return recallMessageResponse {} return RecallMessageResponse.newBuilder().build()
}
@Grpc("MessageService", "GetForwardMessages")
override suspend fun getForwardMessages(request: GetForwardMessagesRequest): GetForwardMessagesResponse {
return getForwardMessagesResponse {
MessageHelper.getForwardMsg(request.resId).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withCause(it))
}.getOrThrow().forEach { detail ->
messages.add(messageBody {
val peer = when (scene) {
Scene.GROUP -> detail.groupId.toString()
Scene.FRIEND -> detail.sender.userId.toString()
else -> detail.peerId.toString()
}
this.time = detail.time
this.scene = when(detail.msgType) {
MsgConstant.KCHATTYPEC2C -> Scene.FRIEND
MsgConstant.KCHATTYPEGROUP -> Scene.GROUP
MsgConstant.KCHATTYPEGUILD -> Scene.GUILD
MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> Scene.STRANGER_FROM_GROUP
MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN -> Scene.NEARBY
else -> Scene.STRANGER
}
this.messageId = detail.qqMsgId
this.messageSeq = detail.msgSeq
this.contact = contact {
this.scene = scene
this.peer = peer
}
this.sender = sender {
this.uin = detail.sender.userId
this.nick = detail.sender.nickName
this.uid = detail.sender.uid
}
detail.message?.elements?.toKritorResponseMessages(Contact(detail.msgType, peer, null))?.let {
this.elements.addAll(it)
}
})
}
}
} }
@Grpc("MessageService", "GetMessage") @Grpc("MessageService", "GetMessage")
override suspend fun getMessage(request: GetMessageRequest): GetMessageResponse { override suspend fun getMessage(request: GetMessageRequest): GetMessageResponse {
val contact = request.contact.let { val contact = request.contact.let {
MessageHelper.generateContact(when(it.scene!!) { MessageHelper.generateContact(
Scene.GROUP -> MsgConstant.KCHATTYPEGROUP when (it.scene!!) {
Scene.FRIEND -> MsgConstant.KCHATTYPEC2C Scene.GROUP -> MsgConstant.KCHATTYPEGROUP
Scene.GUILD -> MsgConstant.KCHATTYPEGUILD Scene.FRIEND -> MsgConstant.KCHATTYPEC2C
Scene.STRANGER_FROM_GROUP -> MsgConstant.KCHATTYPETEMPC2CFROMGROUP Scene.GUILD -> MsgConstant.KCHATTYPEGUILD
Scene.NEARBY -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN Scene.STRANGER_FROM_GROUP -> MsgConstant.KCHATTYPETEMPC2CFROMGROUP
Scene.STRANGER -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN Scene.NEARBY -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene")) Scene.STRANGER -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
}, it.peer, it.subPeer) Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene"))
}, it.peer, it.subPeer
)
} }
val msg: MsgRecord = withTimeoutOrNull(5000) { val msg: MsgRecord = withTimeoutOrNull(5000) {
val service = QRoute.api(IMsgService::class.java) val service = QRoute.api(IMsgService::class.java)
suspendCancellableCoroutine { continuation -> suspendCancellableCoroutine { continuation ->
service.getMsgsByMsgId(contact, arrayListOf(request.messageId)) { code, _, msgRecords -> service.getMsgsByMsgId(contact, arrayListOf(request.messageId.toLong())) { code, _, msgRecords ->
if (code == 0 && msgRecords.isNotEmpty()) { if (code == 0 && msgRecords.isNotEmpty()) {
continuation.resume(msgRecords.first()) continuation.resume(msgRecords.first())
} else { } else {
@ -242,34 +165,35 @@ internal object MessageService: MessageServiceGrpcKt.MessageServiceCoroutineImpl
} }
} ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Message not found")) } ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Message not found"))
return getMessageResponse { return GetMessageResponse.newBuilder().apply {
this.message = messageBody { this.message = PushMessageBody.newBuilder().apply {
this.messageId = msg.msgId this.messageId = msg.msgId.toString()
this.scene = request.contact.scene
this.contact = request.contact this.contact = request.contact
this.sender = sender { this.sender = Sender.newBuilder().apply {
this.uid = msg.senderUid ?: ""
this.uin = msg.senderUin this.uin = msg.senderUin
this.nick = msg.sendNickName ?: "" this.nick = msg.sendNickName ?: ""
this.uid = msg.senderUid ?: "" }.build()
}
this.messageSeq = msg.msgSeq this.messageSeq = msg.msgSeq
this.elements.addAll(msg.elements.toKritorReqMessages(contact)) this.addAllElements(msg.elements.toKritorReqMessages(contact))
} }.build()
} }.build()
} }
@Grpc("MessageService", "GetMessageBySeq") @Grpc("MessageService", "GetMessageBySeq")
override suspend fun getMessageBySeq(request: GetMessageBySeqRequest): GetMessageBySeqResponse { override suspend fun getMessageBySeq(request: GetMessageBySeqRequest): GetMessageBySeqResponse {
val contact = request.contact.let { val contact = request.contact.let {
MessageHelper.generateContact(when(it.scene!!) { MessageHelper.generateContact(
Scene.GROUP -> MsgConstant.KCHATTYPEGROUP when (it.scene!!) {
Scene.FRIEND -> MsgConstant.KCHATTYPEC2C Scene.GROUP -> MsgConstant.KCHATTYPEGROUP
Scene.GUILD -> MsgConstant.KCHATTYPEGUILD Scene.FRIEND -> MsgConstant.KCHATTYPEC2C
Scene.STRANGER_FROM_GROUP -> MsgConstant.KCHATTYPETEMPC2CFROMGROUP Scene.GUILD -> MsgConstant.KCHATTYPEGUILD
Scene.NEARBY -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN Scene.STRANGER_FROM_GROUP -> MsgConstant.KCHATTYPETEMPC2CFROMGROUP
Scene.STRANGER -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN Scene.NEARBY -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene")) Scene.STRANGER -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
}, it.peer, it.subPeer) Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene"))
}, it.peer, it.subPeer
)
} }
val msg: MsgRecord = withTimeoutOrNull(5000) { val msg: MsgRecord = withTimeoutOrNull(5000) {
val service = QRoute.api(IMsgService::class.java) val service = QRoute.api(IMsgService::class.java)
@ -287,39 +211,40 @@ internal object MessageService: MessageServiceGrpcKt.MessageServiceCoroutineImpl
} }
} ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Message not found")) } ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Message not found"))
return getMessageBySeqResponse { return GetMessageBySeqResponse.newBuilder().apply {
this.message = messageBody { this.message = PushMessageBody.newBuilder().apply {
this.messageId = msg.msgId this.messageId = msg.msgId.toString()
this.scene = request.contact.scene
this.contact = request.contact this.contact = request.contact
this.sender = sender { this.sender = Sender.newBuilder().apply {
this.uin = msg.senderUin this.uin = msg.senderUin
this.nick = msg.sendNickName ?: "" this.nick = msg.sendNickName ?: ""
this.uid = msg.senderUid ?: "" this.uid = msg.senderUid ?: ""
} }.build()
this.messageSeq = msg.msgSeq this.messageSeq = msg.msgSeq
this.elements.addAll(msg.elements.toKritorReqMessages(contact)) this.addAllElements(msg.elements.toKritorReqMessages(contact))
} }.build()
} }.build()
} }
@Grpc("MessageService", "GetHistoryMessage") @Grpc("MessageService", "GetHistoryMessage")
override suspend fun getHistoryMessage(request: GetHistoryMessageRequest): GetHistoryMessageResponse { override suspend fun getHistoryMessage(request: GetHistoryMessageRequest): GetHistoryMessageResponse {
val contact = request.contact.let { val contact = request.contact.let {
MessageHelper.generateContact(when(it.scene!!) { MessageHelper.generateContact(
Scene.GROUP -> MsgConstant.KCHATTYPEGROUP when (it.scene!!) {
Scene.FRIEND -> MsgConstant.KCHATTYPEC2C Scene.GROUP -> MsgConstant.KCHATTYPEGROUP
Scene.GUILD -> MsgConstant.KCHATTYPEGUILD Scene.FRIEND -> MsgConstant.KCHATTYPEC2C
Scene.STRANGER_FROM_GROUP -> MsgConstant.KCHATTYPETEMPC2CFROMGROUP Scene.GUILD -> MsgConstant.KCHATTYPEGUILD
Scene.NEARBY -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN Scene.STRANGER_FROM_GROUP -> MsgConstant.KCHATTYPETEMPC2CFROMGROUP
Scene.STRANGER -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN Scene.NEARBY -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene")) Scene.STRANGER -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
}, it.peer, it.subPeer) Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene"))
}, it.peer, it.subPeer
)
} }
val msgs: List<MsgRecord> = withTimeoutOrNull(5000) { val msgs: List<MsgRecord> = withTimeoutOrNull(5000) {
val service = QRoute.api(IMsgService::class.java) val service = QRoute.api(IMsgService::class.java)
suspendCancellableCoroutine { continuation -> 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()) { if (code == 0 && msgRecords.isNotEmpty()) {
continuation.resume(msgRecords) continuation.resume(msgRecords)
} else { } else {
@ -332,31 +257,100 @@ internal object MessageService: MessageServiceGrpcKt.MessageServiceCoroutineImpl
} }
} ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Messages not found")) } ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Messages not found"))
return getHistoryMessageResponse { return GetHistoryMessageResponse.newBuilder().apply {
msgs.forEach { msgs.forEach {
messages.add(messageBody { addMessages(PushMessageBody.newBuilder().apply {
this.messageId = it.msgId this.messageId = it.msgId.toString()
this.scene = request.contact.scene
this.contact = request.contact this.contact = request.contact
this.sender = sender { this.sender = Sender.newBuilder().apply {
this.uin = it.senderUin this.uin = it.senderUin
this.nick = it.sendNickName ?: "" this.nick = it.sendNickName ?: ""
this.uid = it.senderUid ?: "" this.uid = it.senderUid ?: ""
} }.build()
this.messageSeq = it.msgSeq this.messageSeq = it.msgSeq
this.elements.addAll(it.elements.toKritorReqMessages(contact)) this.addAllElements(it.elements.toKritorReqMessages(contact))
}) })
} }
} }.build()
} }
@Grpc("MessageService", "DeleteEssenceMsg") @Grpc("MessageService", "UploadForwardMessage")
override suspend fun deleteEssenceMsg(request: DeleteEssenceMsgRequest): DeleteEssenceMsgResponse { override suspend fun uploadForwardMessage(request: UploadForwardMessageRequest): UploadForwardMessageResponse {
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,
request.messagesList
).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withCause(it))
}.getOrThrow()
return UploadForwardMessageResponse.newBuilder().apply {
this.resId = forwardMessage.resId
}.build()
}
@Grpc("MessageService", "DownloadForwardMessage")
override suspend fun downloadForwardMessage(request: DownloadForwardMessageRequest): DownloadForwardMessageResponse {
return DownloadForwardMessageResponse.newBuilder().apply {
this.addAllMessages(
MessageHelper.getForwardMsg(request.resId).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withCause(it))
}.getOrThrow().map { detail ->
PushMessageBody.newBuilder().apply {
this.time = detail.time
this.messageId = detail.qqMsgId.toString()
this.messageSeq = detail.msgSeq
this.contact = io.kritor.common.Contact.newBuilder().apply {
this.scene = when (detail.msgType) {
MsgConstant.KCHATTYPEC2C -> Scene.FRIEND
MsgConstant.KCHATTYPEGROUP -> Scene.GROUP
MsgConstant.KCHATTYPEGUILD -> Scene.GUILD
MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> Scene.STRANGER_FROM_GROUP
MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN -> Scene.NEARBY
else -> Scene.STRANGER
}
this.peer = detail.peerId.toString()
}.build()
this.sender = Sender.newBuilder().apply {
this.uin = detail.sender.userId
this.nick = detail.sender.nickName
this.uid = detail.sender.uid
}.build()
detail.message?.elements?.toKritorResponseMessages(
com.tencent.qqnt.kernel.nativeinterface.Contact(
detail.msgType,
detail.peerId.toString(),
null
)
)?.let {
this.addAllElements(it)
}
}.build()
}
)
}.build()
}
@Grpc("MessageService", "DeleteEssenceMessage")
override suspend fun deleteEssenceMessage(request: DeleteEssenceMessageRequest): DeleteEssenceMessageResponse {
val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, request.groupId.toString()) val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, request.groupId.toString())
val msg: MsgRecord = withTimeoutOrNull(5000) { val msg: MsgRecord = withTimeoutOrNull(5000) {
val service = QRoute.api(IMsgService::class.java) val service = QRoute.api(IMsgService::class.java)
suspendCancellableCoroutine { continuation -> suspendCancellableCoroutine { continuation ->
service.getMsgsByMsgId(contact, arrayListOf(request.messageId)) { code, _, msgRecords -> service.getMsgsByMsgId(contact, arrayListOf(request.messageId.toLong())) { code, _, msgRecords ->
if (code == 0 && msgRecords.isNotEmpty()) { if (code == 0 && msgRecords.isNotEmpty()) {
continuation.resume(msgRecords.first()) continuation.resume(msgRecords.first())
} else { } else {
@ -368,19 +362,19 @@ internal object MessageService: MessageServiceGrpcKt.MessageServiceCoroutineImpl
} }
} }
} ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Message not found")) } ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Message not found"))
if(MessageHelper.deleteEssenceMessage(request.groupId, msg.msgSeq, msg.msgRandom) == null) if (MessageHelper.deleteEssenceMessage(request.groupId, msg.msgSeq, msg.msgRandom) == null)
throw StatusRuntimeException(Status.NOT_FOUND.withDescription("delete essence message failed")) throw StatusRuntimeException(Status.NOT_FOUND.withDescription("delete essence message failed"))
return deleteEssenceMsgResponse { } return DeleteEssenceMessageResponse.newBuilder().build()
} }
@Grpc("MessageService", "GetEssenceMessages") @Grpc("MessageService", "GetEssenceMessageList")
override suspend fun getEssenceMessages(request: GetEssenceMessagesRequest): GetEssenceMessagesResponse { override suspend fun getEssenceMessageList(request: GetEssenceMessageListRequest): GetEssenceMessageListResponse {
val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, request.groupId.toString()) val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, request.groupId.toString())
return getEssenceMessagesResponse { return GetEssenceMessageListResponse.newBuilder().apply {
MessageHelper.getEssenceMessageList(request.groupId, request.page, request.pageSize).onFailure { MessageHelper.getEssenceMessageList(request.groupId, request.page, request.pageSize).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withCause(it)) throw StatusRuntimeException(Status.INTERNAL.withCause(it))
}.getOrThrow().forEach { }.getOrThrow().forEach {
essenceMessage.add(essenceMessage { addMessages(EssenceMessageBody.newBuilder().apply {
withTimeoutOrNull(5000) { withTimeoutOrNull(5000) {
val service = QRoute.api(IMsgService::class.java) val service = QRoute.api(IMsgService::class.java)
suspendCancellableCoroutine { continuation -> suspendCancellableCoroutine { continuation ->
@ -396,10 +390,10 @@ internal object MessageService: MessageServiceGrpcKt.MessageServiceCoroutineImpl
} }
} }
}?.let { }?.let {
this.messageId = it.msgId this.messageId = it.msgId.toString()
} }
this.messageSeq = it.messageSeq this.messageSeq = it.messageSeq
this.msgTime = it.senderTime.toInt() this.messageTime = it.senderTime.toInt()
this.senderNick = it.senderNick this.senderNick = it.senderNick
this.senderUin = it.senderId this.senderUin = it.senderId
this.operationTime = it.operatorTime.toInt() this.operationTime = it.operatorTime.toInt()
@ -408,7 +402,7 @@ internal object MessageService: MessageServiceGrpcKt.MessageServiceCoroutineImpl
this.jsonElements = it.messageContent.toString() this.jsonElements = it.messageContent.toString()
}) })
} }
} }.build()
} }
@Grpc("MessageService", "SetEssenceMessage") @Grpc("MessageService", "SetEssenceMessage")
@ -417,7 +411,7 @@ internal object MessageService: MessageServiceGrpcKt.MessageServiceCoroutineImpl
val msg: MsgRecord = withTimeoutOrNull(5000) { val msg: MsgRecord = withTimeoutOrNull(5000) {
val service = QRoute.api(IMsgService::class.java) val service = QRoute.api(IMsgService::class.java)
suspendCancellableCoroutine { continuation -> suspendCancellableCoroutine { continuation ->
service.getMsgsByMsgId(contact, arrayListOf(request.messageId)) { code, _, msgRecords -> service.getMsgsByMsgId(contact, arrayListOf(request.messageId.toLong())) { code, _, msgRecords ->
if (code == 0 && msgRecords.isNotEmpty()) { if (code == 0 && msgRecords.isNotEmpty()) {
continuation.resume(msgRecords.first()) continuation.resume(msgRecords.first())
} else { } else {
@ -432,26 +426,28 @@ internal object MessageService: MessageServiceGrpcKt.MessageServiceCoroutineImpl
if (MessageHelper.setEssenceMessage(request.groupId, msg.msgSeq, msg.msgRandom) == null) { if (MessageHelper.setEssenceMessage(request.groupId, msg.msgSeq, msg.msgRandom) == null) {
throw StatusRuntimeException(Status.NOT_FOUND.withDescription("set essence message failed")) throw StatusRuntimeException(Status.NOT_FOUND.withDescription("set essence message failed"))
} }
return setEssenceMessageResponse { } return SetEssenceMessageResponse.newBuilder().build()
} }
@Grpc("MessageService", "SetMessageCommentEmoji") @Grpc("MessageService", "ReactMessageWithEmoji")
override suspend fun setMessageCommentEmoji(request: SetMessageCommentEmojiRequest): SetMessageCommentEmojiResponse { override suspend fun reactMessageWithEmoji(request: ReactMessageWithEmojiRequest): ReactMessageWithEmojiResponse {
val contact = request.contact.let { val contact = request.contact.let {
MessageHelper.generateContact(when(it.scene!!) { MessageHelper.generateContact(
Scene.GROUP -> MsgConstant.KCHATTYPEGROUP when (it.scene!!) {
Scene.FRIEND -> MsgConstant.KCHATTYPEC2C Scene.GROUP -> MsgConstant.KCHATTYPEGROUP
Scene.GUILD -> MsgConstant.KCHATTYPEGUILD Scene.FRIEND -> MsgConstant.KCHATTYPEC2C
Scene.STRANGER_FROM_GROUP -> MsgConstant.KCHATTYPETEMPC2CFROMGROUP Scene.GUILD -> MsgConstant.KCHATTYPEGUILD
Scene.NEARBY -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN Scene.STRANGER_FROM_GROUP -> MsgConstant.KCHATTYPETEMPC2CFROMGROUP
Scene.STRANGER -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN Scene.NEARBY -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene")) Scene.STRANGER -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
}, it.peer, it.subPeer) Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene"))
}, it.peer, it.subPeer
)
} }
val msg: MsgRecord = withTimeoutOrNull(5000) { val msg: MsgRecord = withTimeoutOrNull(5000) {
val service = QRoute.api(IMsgService::class.java) val service = QRoute.api(IMsgService::class.java)
suspendCancellableCoroutine { continuation -> suspendCancellableCoroutine { continuation ->
service.getMsgsByMsgId(contact, arrayListOf(request.messageId)) { code, _, msgRecords -> service.getMsgsByMsgId(contact, arrayListOf(request.messageId.toLong())) { code, _, msgRecords ->
if (code == 0 && msgRecords.isNotEmpty()) { if (code == 0 && msgRecords.isNotEmpty()) {
continuation.resume(msgRecords.first()) continuation.resume(msgRecords.first())
} else { } else {
@ -463,7 +459,12 @@ internal object MessageService: MessageServiceGrpcKt.MessageServiceCoroutineImpl
} }
} }
} ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Message not found")) } ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Message not found"))
MessageHelper.setGroupMessageCommentFace(request.contact.longPeer(), msg.msgSeq.toULong(), request.faceId.toString(), request.isComment) MessageHelper.setGroupMessageCommentFace(
return setMessageCommentEmojiResponse { } request.contact.longPeer(),
msg.msgSeq.toULong(),
request.faceId.toString(),
request.isComment
)
return ReactMessageWithEmojiResponse.newBuilder().build()
} }
} }

View File

@ -0,0 +1,26 @@
package kritor.service
import com.google.protobuf.ByteString
import com.tencent.mobileqq.fe.FEKit
import com.tencent.mobileqq.qsec.qsecdandelionsdk.Dandelion
import io.kritor.developer.*
internal object QsignService: QsignServiceGrpcKt.QsignServiceCoroutineImplBase() {
@Grpc("QsignService", "Sign")
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.secSig = ByteString.copyFrom(result.sign)
this.secDeviceToken = ByteString.copyFrom(result.token)
this.secExtra = ByteString.copyFrom(result.extra)
}.build()
}
@Grpc("QsignService", "Energy")
override suspend fun energy(request: EnergyRequest): EnergyResponse {
return EnergyResponse.newBuilder().apply {
this.result = ByteString.copyFrom(Dandelion.getInstance().fly(request.data, request.salt.toByteArray()))
}.build()
}
}

View File

@ -2,36 +2,24 @@ package kritor.service
import io.grpc.Status import io.grpc.Status
import io.grpc.StatusRuntimeException import io.grpc.StatusRuntimeException
import io.kritor.web.GetCSRFTokenRequest import io.kritor.web.*
import io.kritor.web.GetCSRFTokenResponse
import io.kritor.web.GetCookiesRequest
import io.kritor.web.GetCookiesResponse
import io.kritor.web.GetCredentialsRequest
import io.kritor.web.GetCredentialsResponse
import io.kritor.web.GetHttpCookiesRequest
import io.kritor.web.GetHttpCookiesResponse
import io.kritor.web.WebServiceGrpcKt
import io.kritor.web.getCSRFTokenResponse
import io.kritor.web.getCookiesResponse
import io.kritor.web.getCredentialsResponse
import io.kritor.web.getHttpCookiesResponse
import qq.service.ticket.TicketHelper import qq.service.ticket.TicketHelper
internal object WebService: WebServiceGrpcKt.WebServiceCoroutineImplBase() { internal object WebService: WebServiceGrpcKt.WebServiceCoroutineImplBase() {
@Grpc("WebService", "GetCookies") @Grpc("WebService", "GetCookies")
override suspend fun getCookies(request: GetCookiesRequest): GetCookiesResponse { override suspend fun getCookies(request: GetCookiesRequest): GetCookiesResponse {
return getCookiesResponse { return GetCookiesResponse.newBuilder().apply {
if (request.domain.isNullOrEmpty()) { if (request.domain.isNullOrEmpty()) {
this.cookie = TicketHelper.getCookie() this.cookie = TicketHelper.getCookie()
} else { } else {
this.cookie = TicketHelper.getCookie(request.domain) this.cookie = TicketHelper.getCookie(request.domain)
} }
} }.build()
} }
@Grpc("WebService", "GetCredentials") @Grpc("WebService", "GetCredentials")
override suspend fun getCredentials(request: GetCredentialsRequest): GetCredentialsResponse { override suspend fun getCredentials(request: GetCredentialsRequest): GetCredentialsResponse {
return getCredentialsResponse { return GetCredentialsResponse.newBuilder().apply {
if (request.domain.isNullOrEmpty()) { if (request.domain.isNullOrEmpty()) {
val uin = TicketHelper.getUin() val uin = TicketHelper.getUin()
val skey = TicketHelper.getRealSkey(uin) val skey = TicketHelper.getRealSkey(uin)
@ -46,27 +34,25 @@ internal object WebService: WebServiceGrpcKt.WebServiceCoroutineImplBase() {
this.cookie = "o_cookie=$uin; ied_qq=o$uin; pac_uid=1_$uin; uin=o$uin; skey=$skey; p_uin=o$uin; p_skey=$pskey; pt4_token=$pt4token;" this.cookie = "o_cookie=$uin; ied_qq=o$uin; pac_uid=1_$uin; uin=o$uin; skey=$skey; p_uin=o$uin; p_skey=$pskey; pt4_token=$pt4token;"
this.bkn = TicketHelper.getCSRF(pskey) this.bkn = TicketHelper.getCSRF(pskey)
} }
} }.build()
} }
@Grpc("WebService", "GetCSRFToken") @Grpc("WebService", "GetCSRFToken")
override suspend fun getCSRFToken(request: GetCSRFTokenRequest): GetCSRFTokenResponse { override suspend fun getCSRFToken(request: GetCSRFTokenRequest): GetCSRFTokenResponse {
return getCSRFTokenResponse { return GetCSRFTokenResponse.newBuilder().apply {
if (request.domain.isNullOrEmpty()) { if (request.domain.isNullOrEmpty()) {
this.bkn = TicketHelper.getCSRF() this.bkn = TicketHelper.getCSRF()
} else { } else {
this.bkn = TicketHelper.getCSRF(TicketHelper.getUin(), request.domain) this.bkn = TicketHelper.getCSRF(TicketHelper.getUin(), request.domain)
} }
} }.build()
} }
@Grpc("WebService", "GetHttpCookies") @Grpc("WebService", "GetHttpCookies")
override suspend fun getHttpCookies(request: GetHttpCookiesRequest): GetHttpCookiesResponse { override suspend fun getHttpCookies(request: GetHttpCookiesRequest): GetHttpCookiesResponse {
return getHttpCookiesResponse { return GetHttpCookiesResponse.newBuilder().apply {
this.cookie = TicketHelper.getHttpCookies(request.appid, request.daid, request.jumpUrl) this.cookie = TicketHelper.getHttpCookies(request.appid, request.daid, request.jumpUrl)
?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get http cookies")) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get http cookies"))
} }.build()
} }
} }

View File

@ -14,7 +14,10 @@ import moe.fuqiuluo.shamrock.tools.toast
import moe.fuqiuluo.shamrock.xposed.helper.AppTalker import moe.fuqiuluo.shamrock.xposed.helper.AppTalker
import mqq.app.MobileQQ import mqq.app.MobileQQ
import java.io.File import java.io.File
import java.util.Calendar
import java.util.Date import java.util.Date
import java.util.Timer
import java.util.TimerTask
internal enum class Level( internal enum class Level(
val id: Byte val id: Byte
@ -31,7 +34,29 @@ internal object LogCenter {
// 格式化时间 // 格式化时间
SimpleDateFormat("yyyy-MM-dd").format(Date()) SimpleDateFormat("yyyy-MM-dd").format(Date())
}_" }_"
private val LogFile = MobileQQ.getContext().getExternalFilesDir(null)!! private var LogFile = generateLogFile()
private val format = SimpleDateFormat("[HH:mm:ss] ")
private val timer = Timer()
init {
val now = Calendar.getInstance()
val tomorrowMidnight = Calendar.getInstance().apply {
add(Calendar.DAY_OF_YEAR, 1)
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
val delay = tomorrowMidnight.timeInMillis - now.timeInMillis
timer.scheduleAtFixedRate(object : TimerTask() {
override fun run() {
LogFile = generateLogFile()
}
}, delay, 24 * 60 * 60 * 1000)
}
private fun generateLogFile() = MobileQQ.getContext().getExternalFilesDir(null)!!
.parentFile!!.resolve("Tencent/Shamrock/log").also { .parentFile!!.resolve("Tencent/Shamrock/log").also {
if (it.exists()) it.delete() if (it.exists()) it.delete()
it.mkdirs() it.mkdirs()
@ -49,8 +74,6 @@ internal object LogCenter {
return@let result return@let result
} }
private val format = SimpleDateFormat("[HH:mm:ss] ")
fun log(string: String, level: Level = Level.INFO, toast: Boolean = false) { fun log(string: String, level: Level = Level.INFO, toast: Boolean = false) {
if (!ShamrockConfig[DebugMode] && level == Level.DEBUG) { if (!ShamrockConfig[DebugMode] && level == Level.DEBUG) {
return return

View File

@ -4,37 +4,10 @@ package moe.fuqiuluo.shamrock.internals
import com.tencent.qqnt.kernel.nativeinterface.MsgElement import com.tencent.qqnt.kernel.nativeinterface.MsgElement
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
import io.kritor.event.GroupApplyType import io.kritor.event.*
import io.kritor.event.GroupMemberBanType import io.kritor.common.PushMessageBody
import io.kritor.event.GroupMemberDecreasedType import io.kritor.common.Contact
import io.kritor.event.GroupMemberIncreasedType import io.kritor.common.Sender
import io.kritor.event.MessageEvent
import io.kritor.event.NoticeEvent
import io.kritor.event.NoticeType
import io.kritor.event.RequestType
import io.kritor.event.RequestsEvent
import io.kritor.event.Scene
import io.kritor.event.contact
import io.kritor.event.essenceMessageNotice
import io.kritor.event.friendApplyRequest
import io.kritor.event.friendFileComeNotice
import io.kritor.event.friendPokeNotice
import io.kritor.event.friendRecallNotice
import io.kritor.event.groupAdminChangedNotice
import io.kritor.event.groupApplyRequest
import io.kritor.event.groupFileComeNotice
import io.kritor.event.groupMemberBannedNotice
import io.kritor.event.groupMemberDecreasedNotice
import io.kritor.event.groupMemberIncreasedNotice
import io.kritor.event.groupPokeNotice
import io.kritor.event.groupRecallNotice
import io.kritor.event.groupSignNotice
import io.kritor.event.groupUniqueTitleChangedNotice
import io.kritor.event.groupWholeBanNotice
import io.kritor.event.messageEvent
import io.kritor.event.noticeEvent
import io.kritor.event.requestsEvent
import io.kritor.event.sender
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.FlowCollector
@ -43,9 +16,9 @@ import kotlinx.coroutines.launch
import qq.service.QQInterfaces import qq.service.QQInterfaces
import qq.service.msg.toKritorEventMessages import qq.service.msg.toKritorEventMessages
internal object GlobalEventTransmitter: QQInterfaces() { internal object GlobalEventTransmitter : QQInterfaces() {
private val messageEventFlow by lazy { private val MessageEventFlow by lazy {
MutableSharedFlow<Pair<MsgRecord, MessageEvent>>() MutableSharedFlow<Pair<MsgRecord, PushMessageBody>>()
} }
private val noticeEventFlow by lazy { private val noticeEventFlow by lazy {
MutableSharedFlow<NoticeEvent>() MutableSharedFlow<NoticeEvent>()
@ -58,30 +31,30 @@ internal object GlobalEventTransmitter: QQInterfaces() {
private suspend fun pushRequest(requestEvent: RequestsEvent) = requestEventFlow.emit(requestEvent) private suspend fun pushRequest(requestEvent: RequestsEvent) = requestEventFlow.emit(requestEvent)
private suspend fun transMessageEvent(record: MsgRecord, message: MessageEvent) = messageEventFlow.emit(record to message) private suspend fun transMessageEvent(record: MsgRecord, message: PushMessageBody) =
MessageEventFlow.emit(record to message)
object MessageTransmitter { object MessageTransmitter {
suspend fun transGroupMessage( suspend fun transGroupMessage(
record: MsgRecord, record: MsgRecord,
elements: ArrayList<MsgElement>, elements: ArrayList<MsgElement>,
): Boolean { ): Boolean {
transMessageEvent(record, messageEvent { transMessageEvent(record, PushMessageBody.newBuilder().apply {
this.time = record.msgTime.toInt() this.time = record.msgTime.toInt()
this.scene = Scene.GROUP this.messageId = record.msgId.toString()
this.messageId = record.msgId
this.messageSeq = record.msgSeq this.messageSeq = record.msgSeq
this.contact = contact { this.contact = Contact.newBuilder().apply {
this.scene = scene this.scene = scene
this.peer = record.peerUin.toString() this.peer = record.peerUin.toString()
this.subPeer = record.peerUid this.subPeer = record.peerUid
} }.build()
this.sender = sender { this.sender = Sender.newBuilder().apply {
this.uin = record.senderUin this.uin = record.senderUin
this.uid = record.senderUid this.uid = record.senderUid
this.nick = record.sendNickName this.nick = record.sendNickName
} }.build()
this.elements.addAll(elements.toKritorEventMessages(record)) this.addAllElements(elements.toKritorEventMessages(record))
}) }.build())
return true return true
} }
@ -89,23 +62,22 @@ internal object GlobalEventTransmitter: QQInterfaces() {
record: MsgRecord, record: MsgRecord,
elements: ArrayList<MsgElement>, elements: ArrayList<MsgElement>,
): Boolean { ): Boolean {
transMessageEvent(record, messageEvent { transMessageEvent(record, PushMessageBody.newBuilder().apply {
this.time = record.msgTime.toInt() this.time = record.msgTime.toInt()
this.scene = Scene.FRIEND this.messageId = record.msgId.toString()
this.messageId = record.msgId
this.messageSeq = record.msgSeq this.messageSeq = record.msgSeq
this.contact = contact { this.contact = Contact.newBuilder().apply {
this.scene = scene this.scene = scene
this.peer = record.senderUin.toString() this.peer = record.senderUin.toString()
this.subPeer = record.senderUid this.subPeer = record.senderUid
} }.build()
this.sender = sender { this.sender = Sender.newBuilder().apply {
this.uin = record.senderUin this.uin = record.senderUin
this.uid = record.senderUid this.uid = record.senderUid
this.nick = record.sendNickName this.nick = record.sendNickName
} }.build()
this.elements.addAll(elements.toKritorEventMessages(record)) this.addAllElements(elements.toKritorEventMessages(record))
}) }.build())
return true return true
} }
@ -115,23 +87,22 @@ internal object GlobalEventTransmitter: QQInterfaces() {
groupCode: Long, groupCode: Long,
fromNick: String, fromNick: String,
): Boolean { ): Boolean {
transMessageEvent(record, messageEvent { transMessageEvent(record, PushMessageBody.newBuilder().apply {
this.time = record.msgTime.toInt() this.time = record.msgTime.toInt()
this.scene = Scene.FRIEND this.messageId = record.msgId.toString()
this.messageId = record.msgId
this.messageSeq = record.msgSeq this.messageSeq = record.msgSeq
this.contact = contact { this.contact = Contact.newBuilder().apply {
this.scene = scene this.scene = scene
this.peer = record.senderUin.toString() this.peer = record.senderUin.toString()
this.subPeer = groupCode.toString() this.subPeer = groupCode.toString()
} }.build()
this.sender = sender { this.sender = Sender.newBuilder().apply {
this.uin = record.senderUin this.uin = record.senderUin
this.uid = record.senderUid this.uid = record.senderUid
this.nick = record.sendNickName this.nick = record.sendNickName
} }.build()
this.elements.addAll(elements.toKritorEventMessages(record)) this.addAllElements(elements.toKritorEventMessages(record))
}) }.build())
return true return true
} }
@ -139,23 +110,22 @@ internal object GlobalEventTransmitter: QQInterfaces() {
record: MsgRecord, record: MsgRecord,
elements: ArrayList<MsgElement>, elements: ArrayList<MsgElement>,
): Boolean { ): Boolean {
transMessageEvent(record, messageEvent { transMessageEvent(record, PushMessageBody.newBuilder().apply {
this.time = record.msgTime.toInt() this.time = record.msgTime.toInt()
this.scene = Scene.GUILD this.messageId = record.msgId.toString()
this.messageId = record.msgId
this.messageSeq = record.msgSeq this.messageSeq = record.msgSeq
this.contact = contact { this.contact = Contact.newBuilder().apply {
this.scene = scene this.scene = scene
this.peer = record.guildId ?: "" this.peer = record.guildId ?: ""
this.subPeer = record.channelId ?: "" this.subPeer = record.channelId ?: ""
} }.build()
this.sender = sender { this.sender = Sender.newBuilder().apply {
this.uin = record.senderUin this.uin = record.senderUin
this.uid = record.senderUid this.uid = record.senderUid
this.nick = record.sendNickName this.nick = record.sendNickName
} }.build()
this.elements.addAll(elements.toKritorEventMessages(record)) this.addAllElements(elements.toKritorEventMessages(record))
}) }.build())
return true return true
} }
} }
@ -177,19 +147,19 @@ internal object GlobalEventTransmitter: QQInterfaces() {
expireTime: Long, expireTime: Long,
url: String url: String
): Boolean { ): Boolean {
pushNotice(noticeEvent { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.FRIEND_FILE_COME this.type = NoticeEvent.NoticeType.FRIEND_FILE_COME
this.time = msgTime.toInt() this.time = msgTime.toInt()
this.friendFileCome = friendFileComeNotice { this.friendFileUploaded = FriendFileUploadedNotice.newBuilder().apply {
this.fileId = fileId this.fileId = fileId
this.fileName = fileName this.fileName = fileName
this.operator = userId this.operatorUin = userId
this.fileSize = fileSize this.fileSize = fileSize
this.expireTime = expireTime.toInt() this.expireTime = expireTime.toInt()
this.fileSubId = fileSubId this.fileSubId = fileSubId
this.url = url this.url = url
} }.build()
}) }.build())
return true return true
} }
@ -206,19 +176,19 @@ internal object GlobalEventTransmitter: QQInterfaces() {
bizId: Int, bizId: Int,
url: String url: String
): Boolean { ): Boolean {
pushNotice(noticeEvent { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_FILE_COME this.type = NoticeEvent.NoticeType.GROUP_FILE_COME
this.time = msgTime.toInt() this.time = msgTime.toInt()
this.groupFileCome = groupFileComeNotice { this.groupFileUploaded = GroupFileUploadedNotice.newBuilder().apply {
this.groupId = groupId this.groupId = groupId
this.operator = userId this.operatorUin = userId
this.fileId = uuid this.fileId = uuid
this.fileName = fileName this.fileName = fileName
this.fileSize = fileSize this.fileSize = fileSize
this.biz = bizId this.biz = bizId
this.url = url this.url = url
} }.build()
}) }.build())
return true return true
} }
} }
@ -227,33 +197,48 @@ internal object GlobalEventTransmitter: QQInterfaces() {
* 群聊通知 通知器 * 群聊通知 通知器
*/ */
object GroupNoticeTransmitter { object GroupNoticeTransmitter {
suspend fun transGroupSign(time: Long, target: Long, action: String?, rankImg: String?, groupCode: Long): Boolean { suspend fun transGroupSign(
pushNotice(noticeEvent { time: Long,
this.type = NoticeType.GROUP_SIGN target: Long,
action: String?,
rankImg: String?,
groupCode: Long
): Boolean {
pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeEvent.NoticeType.GROUP_SIGN
this.time = time.toInt() this.time = time.toInt()
this.groupSign = groupSignNotice { this.groupSignIn = GroupSignInNotice.newBuilder().apply {
this.groupId = groupCode this.groupId = groupCode
this.targetUin = target this.targetUin = target
this.action = action ?: "" this.action = action ?: ""
this.suffix = "" this.suffix = ""
this.rankImage = rankImg ?: "" this.rankImage = rankImg ?: ""
} }.build()
}) }.build())
return true return true
} }
suspend fun transGroupPoke(time: Long, operator: Long, target: Long, action: String?, suffix: String?, actionImg: String?, groupCode: Long): Boolean { suspend fun transGroupPoke(
pushNotice(noticeEvent { time: Long,
this.type = NoticeType.GROUP_POKE operator: Long,
target: Long,
action: String?,
suffix: String?,
actionImg: String?,
groupCode: Long
): Boolean {
pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeEvent.NoticeType.GROUP_POKE
this.time = time.toInt() this.time = time.toInt()
this.groupPoke = groupPokeNotice { this.groupPoke = GroupPokeNotice.newBuilder().apply {
this.groupId = groupCode
this.action = action ?: "" this.action = action ?: ""
this.target = target this.targetUin = target
this.operator = operator this.operatorUin = operator
this.suffix = suffix ?: "" this.suffix = suffix ?: ""
this.actionImage = actionImg ?: "" this.actionImage = actionImg ?: ""
} }.build()
}) }.build())
return true return true
} }
@ -264,20 +249,20 @@ internal object GlobalEventTransmitter: QQInterfaces() {
groupCode: Long, groupCode: Long,
operator: Long, operator: Long,
operatorUid: String, operatorUid: String,
type: GroupMemberIncreasedType type: GroupMemberIncreasedNotice.GroupMemberIncreasedType
): Boolean { ): Boolean {
pushNotice(noticeEvent { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_MEMBER_INCREASE this.type = NoticeEvent.NoticeType.GROUP_MEMBER_INCREASE
this.time = time.toInt() this.time = time.toInt()
this.groupMemberIncrease = groupMemberIncreasedNotice { this.groupMemberIncrease = GroupMemberIncreasedNotice.newBuilder().apply {
this.groupId = groupCode this.groupId = groupCode
this.operatorUid = operatorUid this.operatorUid = operatorUid
this.operatorUin = operator this.operatorUin = operator
this.targetUid = targetUid this.targetUid = targetUid
this.targetUin = target this.targetUin = target
this.type = type this.type = type
} }.build()
}) }.build())
return true return true
} }
@ -288,20 +273,20 @@ internal object GlobalEventTransmitter: QQInterfaces() {
groupCode: Long, groupCode: Long,
operator: Long, operator: Long,
operatorUid: String, operatorUid: String,
type: GroupMemberDecreasedType type: GroupMemberDecreasedNotice.GroupMemberDecreasedType
): Boolean { ): Boolean {
pushNotice(noticeEvent { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_MEMBER_INCREASE this.type = NoticeEvent.NoticeType.GROUP_MEMBER_INCREASE
this.time = time.toInt() this.time = time.toInt()
this.groupMemberDecrease = groupMemberDecreasedNotice { this.groupMemberDecrease = GroupMemberDecreasedNotice.newBuilder().apply {
this.groupId = groupCode this.groupId = groupCode
this.operatorUid = operatorUid this.operatorUid = operatorUid
this.operatorUin = operator this.operatorUin = operator
this.targetUid = targetUid this.targetUid = targetUid
this.targetUin = target this.targetUin = target
this.type = type this.type = type
} }.build()
}) }.build())
return true return true
} }
@ -312,16 +297,16 @@ internal object GlobalEventTransmitter: QQInterfaces() {
groupCode: Long, groupCode: Long,
setAdmin: Boolean setAdmin: Boolean
): Boolean { ): Boolean {
pushNotice(noticeEvent { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_ADMIN_CHANGED this.type = NoticeEvent.NoticeType.GROUP_ADMIN_CHANGED
this.time = msgTime.toInt() this.time = msgTime.toInt()
this.groupAdminChanged = groupAdminChangedNotice { this.groupAdminChange = GroupAdminChangedNotice.newBuilder().apply {
this.groupId = groupCode this.groupId = groupCode
this.targetUid = targetUid this.targetUid = targetUid
this.targetUin = target this.targetUin = target
this.isAdmin = setAdmin this.isAdmin = setAdmin
} }.build()
}) }.build())
return true return true
} }
@ -331,15 +316,15 @@ internal object GlobalEventTransmitter: QQInterfaces() {
groupCode: Long, groupCode: Long,
isOpen: Boolean isOpen: Boolean
): Boolean { ): Boolean {
pushNotice(noticeEvent { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_WHOLE_BAN this.type = NoticeEvent.NoticeType.GROUP_WHOLE_BAN
this.time = msgTime.toInt() this.time = msgTime.toInt()
this.groupWholeBan = groupWholeBanNotice { this.groupWholeBan = GroupWholeBanNotice.newBuilder().apply {
this.groupId = groupCode this.groupId = groupCode
this.isWholeBan = isOpen this.isBan = isOpen
this.operator = operator this.operatorUin = operator
} }.build()
}) }.build())
return true return true
} }
@ -352,20 +337,20 @@ internal object GlobalEventTransmitter: QQInterfaces() {
groupCode: Long, groupCode: Long,
duration: Int duration: Int
): Boolean { ): Boolean {
pushNotice(noticeEvent { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_MEMBER_BANNED this.type = NoticeEvent.NoticeType.GROUP_MEMBER_BANNED
this.time = msgTime.toInt() this.time = msgTime.toInt()
this.groupMemberBanned = groupMemberBannedNotice { this.groupMemberBan = GroupMemberBanNotice.newBuilder().apply {
this.groupId = groupCode this.groupId = groupCode
this.operatorUid = operatorUid this.operatorUid = operatorUid
this.operatorUin = operator this.operatorUin = operator
this.targetUid = targetUid this.targetUid = targetUid
this.targetUin = target this.targetUin = target
this.duration = duration this.duration = duration
this.type = if (duration > 0) GroupMemberBanType.BAN this.type = if (duration > 0) GroupMemberBanNotice.GroupMemberBanType.BAN
else GroupMemberBanType.LIFT_BAN else GroupMemberBanNotice.GroupMemberBanType.LIFT_BAN
} }.build()
}) }.build())
return true return true
} }
@ -379,30 +364,37 @@ internal object GlobalEventTransmitter: QQInterfaces() {
msgId: Long, msgId: Long,
tipText: String tipText: String
): Boolean { ): Boolean {
pushNotice(noticeEvent { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_RECALL this.type = NoticeEvent.NoticeType.GROUP_RECALL
this.time = time.toInt() this.time = time.toInt()
this.groupRecall = groupRecallNotice { this.groupRecall = GroupRecallNotice.newBuilder().apply {
this.groupId = groupCode this.groupId = groupCode
this.operatorUid = operatorUid this.operatorUid = operatorUid
this.operatorUin = operator this.operatorUin = operator
this.targetUid = targetUid this.targetUid = targetUid
this.targetUin = target this.targetUin = target
this.messageId = msgId this.messageId = msgId.toString()
this.tipText = tipText this.tipText = tipText
} }.build()
}) }.build())
return true return true
} }
suspend fun transCardChange( suspend fun transCardChange(
time: Long, time: Long,
targetId: Long, targetId: Long,
oldCard: String,
newCard: String, newCard: String,
groupId: Long groupId: Long
): Boolean { ): 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 return true
} }
@ -412,15 +404,15 @@ internal object GlobalEventTransmitter: QQInterfaces() {
title: String, title: String,
groupId: Long groupId: Long
): Boolean { ): Boolean {
pushNotice(noticeEvent { 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.time = time.toInt()
this.groupMemberUniqueTitleChanged = groupUniqueTitleChangedNotice { this.groupMemberUniqueTitleChanged = GroupUniqueTitleChangedNotice.newBuilder().apply {
this.groupId = groupId this.groupId = groupId
this.target = targetId this.target = targetId
this.title = title this.title = title
} }.build()
}) }.build())
return true return true
} }
@ -432,17 +424,17 @@ internal object GlobalEventTransmitter: QQInterfaces() {
groupId: Long, groupId: Long,
subType: UInt subType: UInt
): Boolean { ): Boolean {
pushNotice(noticeEvent { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_ESSENCE_CHANGED this.type = NoticeEvent.NoticeType.GROUP_ESSENCE_CHANGED
this.time = time.toInt() this.time = time.toInt()
this.groupEssenceChanged = essenceMessageNotice { this.groupEssenceChanged = GroupEssenceMessageNotice.newBuilder().apply {
this.groupId = groupId this.groupId = groupId
this.messageId = msgId this.messageId = msgId.toString()
this.sender = senderUin this.targetUin = senderUin
this.operator = operatorUin this.operatorUin = operatorUin
this.subType = subType.toInt() this.subType = subType.toInt()
} }.build()
}) }.build())
return true return true
} }
} }
@ -451,31 +443,37 @@ internal object GlobalEventTransmitter: QQInterfaces() {
* 私聊通知 通知器 * 私聊通知 通知器
*/ */
object PrivateNoticeTransmitter { object PrivateNoticeTransmitter {
suspend fun transPrivatePoke(msgTime: Long, operator: Long, target: Long, action: String?, suffix: String?, actionImg: String?): Boolean { suspend fun transPrivatePoke(
pushNotice(noticeEvent { msgTime: Long,
this.type = NoticeType.FRIEND_POKE operator: Long,
target: Long,
action: String?,
suffix: String?,
actionImg: String?
): Boolean {
pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeEvent.NoticeType.FRIEND_POKE
this.time = msgTime.toInt() this.time = msgTime.toInt()
this.friendPoke = friendPokeNotice { this.friendPoke = FriendPokeNotice.newBuilder().apply {
this.action = action ?: "" this.action = action ?: ""
this.target = target this.operatorUin = operator
this.operator = operator
this.suffix = suffix ?: "" this.suffix = suffix ?: ""
this.actionImage = actionImg ?: "" this.actionImage = actionImg ?: ""
} }.build()
}) }.build())
return true return true
} }
suspend fun transPrivateRecall(time: Long, operator: Long, msgId: Long, tipText: String): Boolean { suspend fun transPrivateRecall(time: Long, operator: Long, msgId: Long, tipText: String): Boolean {
pushNotice(noticeEvent { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.FRIEND_RECALL this.type = NoticeEvent.NoticeType.FRIEND_RECALL
this.time = time.toInt() this.time = time.toInt()
this.friendRecall = friendRecallNotice { this.friendRecall = FriendRecallNotice.newBuilder().apply {
this.operator = operator this.operatorUin = operator
this.messageId = msgId this.messageId = msgId.toString()
this.tipText = tipText this.tipText = tipText
} }.build()
}) }.build())
return true return true
} }
@ -486,45 +484,43 @@ internal object GlobalEventTransmitter: QQInterfaces() {
*/ */
object RequestTransmitter { object RequestTransmitter {
suspend fun transFriendApp(time: Long, operator: Long, tipText: String, flag: String): Boolean { suspend fun transFriendApp(time: Long, operator: Long, tipText: String, flag: String): Boolean {
pushRequest(requestsEvent { pushRequest(RequestsEvent.newBuilder().apply {
this.type = RequestType.FRIEND_APPLY this.type = RequestsEvent.RequestType.FRIEND_APPLY
this.time = time.toInt() this.time = time.toInt()
this.friendApply = friendApplyRequest { this.friendApply = FriendApplyRequest.newBuilder().apply {
this.applierUin = operator this.applierUin = operator
this.message = tipText this.message = tipText
this.flag = flag this.flag = flag
} }.build()
}) }.build())
return true return true
} }
suspend fun transGroupApply( suspend fun transGroupApply(
time: Long, time: Long,
applier: Long, applierUin: Long,
applierUid: String, applierUid: String,
reason: String, reason: String,
groupCode: Long, groupCode: Long,
flag: String, flag: String
type: GroupApplyType
): Boolean { ): Boolean {
pushRequest(requestsEvent { pushRequest(RequestsEvent.newBuilder().apply {
this.type = RequestType.GROUP_APPLY this.type = RequestsEvent.RequestType.GROUP_APPLY
this.time = time.toInt() this.time = time.toInt()
this.groupApply = groupApplyRequest { this.groupApply = GroupApplyRequest.newBuilder().apply {
this.applierUid = applierUid this.applierUid = applierUid
this.applierUin = applier this.applierUin = applierUin
this.groupId = groupCode this.groupId = groupCode
this.reason = reason this.reason = reason
this.flag = flag this.flag = flag
this.type = type }.build()
} }.build())
})
return true return true
} }
} }
suspend inline fun onMessageEvent(collector: FlowCollector<Pair<MsgRecord, MessageEvent>>) { suspend inline fun onMessageEvent(collector: FlowCollector<Pair<MsgRecord, PushMessageBody>>) {
messageEventFlow.collect { MessageEventFlow.collect {
GlobalScope.launch { GlobalScope.launch {
collector.emit(it) collector.emit(it)
} }

View File

@ -39,7 +39,7 @@ class AntiDetection: IAction {
if (ShamrockConfig[AntiJvmTrace]) if (ShamrockConfig[AntiJvmTrace])
antiTrace() antiTrace()
antiMemoryWalking() antiMemoryWalking()
antiO3Report() //antiO3Report()
} }
private fun antiO3Report() { private fun antiO3Report() {

View File

@ -9,11 +9,13 @@ import qq.service.QQInterfaces
object SwitchStatus: IInteract, QQInterfaces() { object SwitchStatus: IInteract, QQInterfaces() {
override fun invoke(intent: Intent) { override fun invoke(intent: Intent) {
AppTalker.talk("switch_status") { if (app.isLogin) {
put("account", app.currentAccountUin) AppTalker.talk("switch_status") {
put("nickname", if (app is QQAppInterface) app.currentNickname else "unknown") put("account", app.currentAccountUin)
put("voice", NativeLoader.isVoiceLoaded) put("nickname", if (app is QQAppInterface) (app.currentNickname ?: "unknown") else "unknown")
put("core_version", ShamrockVersion) put("voice", NativeLoader.isVoiceLoaded)
put("core_version", ShamrockVersion)
}
} }
} }
} }

View File

@ -2,7 +2,7 @@ package qq.service.contact
import com.tencent.qqnt.kernel.nativeinterface.Contact import com.tencent.qqnt.kernel.nativeinterface.Contact
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import io.kritor.message.Scene import io.kritor.common.Scene
suspend fun Contact.longPeer(): Long { suspend fun Contact.longPeer(): Long {
return when(this.chatType) { 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) { return when(this.scene) {
Scene.GROUP -> peer.toLong() Scene.GROUP -> peer.toLong()
Scene.FRIEND, Scene.STRANGER, Scene.STRANGER_FROM_GROUP -> if (peer.startsWith("u_")) ContactHelper.getUinByUidAsync(peer).toLong() else peer.toLong() Scene.FRIEND, Scene.STRANGER, Scene.STRANGER_FROM_GROUP -> if (peer.startsWith("u_")) ContactHelper.getUinByUidAsync(peer).toLong() else peer.toLong()

View File

@ -5,15 +5,7 @@ package qq.service.file
import com.tencent.mobileqq.pb.ByteStringMicro import com.tencent.mobileqq.pb.ByteStringMicro
import io.grpc.Status import io.grpc.Status
import io.grpc.StatusRuntimeException import io.grpc.StatusRuntimeException
import io.kritor.file.File import io.kritor.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.folder
import io.kritor.file.getFileSystemInfoResponse
import io.kritor.file.getFilesRequest
import io.kritor.file.getFilesResponse
import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY
@ -73,15 +65,15 @@ internal object GroupFileHelper: QQInterfaces() {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to fetch oidb response x2")) throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to fetch oidb response x2"))
} }
return getFileSystemInfoResponse { return GetFileSystemInfoResponse.newBuilder().apply {
this.fileCount = fileCnt this.fileCount = fileCnt
this.totalCount = limitCnt this.totalCount = limitCnt
this.totalSpace = totalSpace.toInt() this.totalSpace = totalSpace.toInt()
this.usedSpace = usedSpace.toInt() this.usedSpace = usedSpace.toInt()
} }.build()
} }
suspend fun getGroupFiles(groupId: Long, folderId: String = "/"): GetFilesResponse { suspend fun getGroupFiles(groupId: Long, folderId: String = "/"): GetFileListResponse {
val fileSystemInfo = getGroupFileSystemInfo(groupId) val fileSystemInfo = getGroupFileSystemInfo(groupId)
val fromServiceMsg = sendOidbAW("OidbSvc.0x6d8_1", 1752, 1, oidb_0x6d8.ReqBody().also { val fromServiceMsg = sendOidbAW("OidbSvc.0x6d8_1", 1752, 1, oidb_0x6d8.ReqBody().also {
it.file_list_info_req.set(oidb_0x6d8.GetFileListReqBody().apply { it.file_list_info_req.set(oidb_0x6d8.GetFileListReqBody().apply {
@ -108,7 +100,7 @@ internal object GroupFileHelper: QQInterfaces() {
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed")) throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
} }
val files = arrayListOf<File>() val files = arrayListOf<File>()
val dirs = arrayListOf<Folder>() val folders = arrayListOf<Folder>()
if (fromServiceMsg.wupBuffer != null) { if (fromServiceMsg.wupBuffer != null) {
val oidb = oidb_sso.OIDBSSOPkg().mergeFrom(fromServiceMsg.wupBuffer.slice(4).let { val oidb = oidb_sso.OIDBSSOPkg().mergeFrom(fromServiceMsg.wupBuffer.slice(4).let {
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
@ -119,7 +111,7 @@ internal object GroupFileHelper: QQInterfaces() {
rpt_item_list.get().forEach { file -> rpt_item_list.get().forEach { file ->
if (file.uint32_type.get() == oidb_0x6d8.GetFileListRspBody.TYPE_FILE) { if (file.uint32_type.get() == oidb_0x6d8.GetFileListRspBody.TYPE_FILE) {
val fileInfo = file.file_info val fileInfo = file.file_info
files.add(io.kritor.file.file { files.add(File.newBuilder().apply {
this.fileId = fileInfo.str_file_id.get() this.fileId = fileInfo.str_file_id.get()
this.fileName = fileInfo.str_file_name.get() this.fileName = fileInfo.str_file_name.get()
this.fileSize = fileInfo.uint64_file_size.get() this.fileSize = fileInfo.uint64_file_size.get()
@ -133,18 +125,18 @@ internal object GroupFileHelper: QQInterfaces() {
this.sha = fileInfo.bytes_sha.get().toByteArray().toHexString() this.sha = fileInfo.bytes_sha.get().toByteArray().toHexString()
this.sha3 = fileInfo.bytes_sha3.get().toByteArray().toHexString() this.sha3 = fileInfo.bytes_sha3.get().toByteArray().toHexString()
this.md5 = fileInfo.bytes_md5.get().toByteArray().toHexString() this.md5 = fileInfo.bytes_md5.get().toByteArray().toHexString()
}) }.build())
} }
else if (file.uint32_type.get() == oidb_0x6d8.GetFileListRspBody.TYPE_FOLDER) { else if (file.uint32_type.get() == oidb_0x6d8.GetFileListRspBody.TYPE_FOLDER) {
val folderInfo = file.folder_info val folderInfo = file.folder_info
dirs.add(folder { folders.add(Folder.newBuilder().apply {
this.folderId = folderInfo.str_folder_id.get() this.folderId = folderInfo.str_folder_id.get()
this.folderName = folderInfo.str_folder_name.get() this.folderName = folderInfo.str_folder_name.get()
this.totalFileCount = folderInfo.uint32_total_file_count.get() this.totalFileCount = folderInfo.uint32_total_file_count.get()
this.createTime = folderInfo.uint32_create_time.get() this.createTime = folderInfo.uint32_create_time.get()
this.creator = folderInfo.uint64_create_uin.get() this.creator = folderInfo.uint64_create_uin.get()
this.creatorName = folderInfo.str_creator_name.get() this.creatorName = folderInfo.str_creator_name.get()
}) }.build())
} else { } else {
LogCenter.log("未知文件类型: ${file.uint32_type.get()}", Level.WARN) LogCenter.log("未知文件类型: ${file.uint32_type.get()}", Level.WARN)
} }
@ -154,9 +146,9 @@ internal object GroupFileHelper: QQInterfaces() {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to fetch oidb response")) throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to fetch oidb response"))
} }
return getFilesResponse { return GetFileListResponse.newBuilder().apply {
this.files.addAll(files) this.addAllFiles(files)
this.folders.addAll(folders) this.addAllFolders(folders)
} }.build()
} }
} }

View File

@ -14,7 +14,7 @@ import qq.service.bdh.RichProtoSvc
import qq.service.kernel.SimpleKernelMsgListener import qq.service.kernel.SimpleKernelMsgListener
import qq.service.msg.MessageHelper import qq.service.msg.MessageHelper
object AioListener: SimpleKernelMsgListener() { object AioListener : SimpleKernelMsgListener() {
override fun onRecvMsg(records: ArrayList<MsgRecord>) { override fun onRecvMsg(records: ArrayList<MsgRecord>) {
records.forEach { records.forEach {
GlobalScope.launch { GlobalScope.launch {
@ -60,7 +60,12 @@ object AioListener: SimpleKernelMsgListener() {
LogCenter.log("私聊临时消息(private = ${record.senderUin}, groupId=$groupCode)") 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) LogCenter.log("私聊临时消息推送失败 -> MessageTransmitter", Level.WARN)
} }
@ -137,4 +142,10 @@ object AioListener: SimpleKernelMsgListener() {
LogCenter.log("群聊文件消息推送失败 -> FileNoticeTransmitter", Level.WARN) LogCenter.log("群聊文件消息推送失败 -> FileNoticeTransmitter", Level.WARN)
} }
} }
@OptIn(ExperimentalStdlibApi::class)
override fun onRecvSysMsg(arrayList: ArrayList<Byte>?) {
LogCenter.log("onRecvSysMsg")
LogCenter.log(arrayList?.toByteArray()?.toHexString() ?: "")
}
} }

View File

@ -5,9 +5,8 @@ import com.tencent.mobileqq.qroute.QRoute
import com.tencent.qphone.base.remote.FromServiceMsg import com.tencent.qphone.base.remote.FromServiceMsg
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.msg.api.IMsgService import com.tencent.qqnt.msg.api.IMsgService
import io.kritor.event.GroupApplyType import io.kritor.event.GroupMemberDecreasedNotice.GroupMemberDecreasedType
import io.kritor.event.GroupMemberDecreasedType import io.kritor.event.GroupMemberIncreasedNotice.GroupMemberIncreasedType
import io.kritor.event.GroupMemberIncreasedType
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -595,7 +594,7 @@ internal object PrimitiveListener {
} }
LogCenter.log("入群申请($groupCode) $applier: \"$reason\", seq: $msgSeq") LogCenter.log("入群申请($groupCode) $applier: \"$reason\", seq: $msgSeq")
if (!GlobalEventTransmitter.RequestTransmitter 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) LogCenter.log("入群申请推送失败!", Level.WARN)
} }
@ -630,7 +629,7 @@ internal object PrimitiveListener {
} }
LogCenter.log("邀请入群申请($groupCode): $applier") LogCenter.log("邀请入群申请($groupCode): $applier")
if (!GlobalEventTransmitter.RequestTransmitter if (!GlobalEventTransmitter.RequestTransmitter
.transGroupApply(time, applier, applierUid, "", groupCode, flag, GroupApplyType.GROUP_APPLY_ADD) .transGroupApply(time, applier, applierUid, "", groupCode, flag)
) { ) {
LogCenter.log("邀请入群申请推送失败!", Level.WARN) LogCenter.log("邀请入群申请推送失败!", Level.WARN)
} }
@ -658,7 +657,7 @@ internal object PrimitiveListener {
"$time;$groupCode;$uin" "$time;$groupCode;$uin"
} }
if (!GlobalEventTransmitter.RequestTransmitter if (!GlobalEventTransmitter.RequestTransmitter
.transGroupApply(time, invitor, invitorUid, "", groupCode, flag, GroupApplyType.GROUP_APPLY_INVITE) .transGroupApply(time, invitor, invitorUid, "", groupCode, flag)
) { ) {
LogCenter.log("邀请入群推送失败!", Level.WARN) LogCenter.log("邀请入群推送失败!", Level.WARN)
} }

View File

@ -1,19 +1,15 @@
package qq.service.msg package qq.service.msg
import com.tencent.mobileqq.qroute.QRoute 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.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
import com.tencent.qqnt.msg.api.IMsgService import com.tencent.qqnt.msg.api.IMsgService
import io.grpc.Status import io.grpc.Status
import io.grpc.StatusRuntimeException import io.grpc.StatusRuntimeException
import io.kritor.message.Element import io.kritor.common.ForwardElement
import io.kritor.message.ElementType import io.kritor.common.ForwardMessageBody
import io.kritor.message.ForwardElement import io.kritor.common.Scene
import io.kritor.message.ForwardMessageBody
import io.kritor.message.Scene
import io.kritor.message.forwardElement
import io.kritor.message.nodeOrNull
import io.kritor.message.senderOrNull
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.Level
@ -28,16 +24,14 @@ import qq.service.QQInterfaces
import qq.service.contact.ContactHelper import qq.service.contact.ContactHelper
import qq.service.msg.MessageHelper.getMultiMsg import qq.service.msg.MessageHelper.getMultiMsg
import qq.service.ticket.TicketHelper import qq.service.ticket.TicketHelper
import java.util.UUID import java.util.*
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.random.Random import kotlin.random.Random
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
internal object ForwardMessageHelper: QQInterfaces() { internal object ForwardMessageHelper : QQInterfaces() {
suspend fun uploadMultiMsg( suspend fun uploadMultiMsg(
chatType: Int, contact: Contact,
peerId: String,
fromId: String = peerId,
messages: List<ForwardMessageBody>, messages: List<ForwardMessageBody>,
): Result<ForwardElement> { ): Result<ForwardElement> {
var i = -1 var i = -1
@ -46,116 +40,121 @@ internal object ForwardMessageHelper: QQInterfaces() {
val msgs = messages.mapNotNull { msg -> val msgs = messages.mapNotNull { msg ->
kotlin.runCatching { kotlin.runCatching {
val contact = msg.contact.let { when (msg.forwardMessageCase) {
MessageHelper.generateContact(when(it.scene!!) { ForwardMessageBody.ForwardMessageCase.MESSAGE_ID -> {
Scene.GROUP -> MsgConstant.KCHATTYPEGROUP val record: MsgRecord = withTimeoutOrNull(5000) {
Scene.FRIEND -> MsgConstant.KCHATTYPEC2C val service = QRoute.api(IMsgService::class.java)
Scene.GUILD -> MsgConstant.KCHATTYPEGUILD suspendCancellableCoroutine { continuation ->
Scene.STRANGER_FROM_GROUP -> MsgConstant.KCHATTYPETEMPC2CFROMGROUP service.getMsgsByMsgId(
Scene.NEARBY -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN contact,
Scene.STRANGER -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN arrayListOf(msg.messageId.toLong())
Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene")) ) { code, _, msgRecords ->
}, it.peer, it.subPeer) if (code == 0 && msgRecords.isNotEmpty()) {
} continuation.resume(msgRecords.first())
val node = msg.elementsList.find { it.type == ElementType.NODE }?.nodeOrNull } else {
if (node != null) { continuation.resume(null)
val msgId = node.messageId }
val record: MsgRecord = withTimeoutOrNull(5000) { }
val service = QRoute.api(IMsgService::class.java) continuation.invokeOnCancellation {
suspendCancellableCoroutine { continuation ->
service.getMsgsByMsgId(contact, arrayListOf(msgId)) { code, _, msgRecords ->
if (code == 0 && msgRecords.isNotEmpty()) {
continuation.resume(msgRecords.first())
} else {
continuation.resume(null) continuation.resume(null)
} }
} }
continuation.invokeOnCancellation { } ?: error("合并转发消息节点消息(id = ${msg.messageId})获取失败")
continuation.resume(null) PushMsgBody(
} msgHead = ResponseHead(
} peerUid = record.senderUid,
} ?: error("合并转发消息节点消息(id = $msgId)获取失败") receiverUid = record.peerUid,
PushMsgBody( forward = ResponseForward(
msgHead = ResponseHead( friendName = record.sendNickName
peerUid = record.senderUid, ),
receiverUid = record.peerUid, responseGrp = if (record.chatType == MsgConstant.KCHATTYPEGROUP) ResponseGrp(
forward = ResponseForward( groupCode = record.peerUin.toULong(),
friendName = record.sendNickName memberCard = record.sendMemberName,
u1 = 2
) else null
), ),
responseGrp = if (record.chatType == MsgConstant.KCHATTYPEGROUP) ResponseGrp( contentHead = ContentHead(
groupCode = record.peerUin.toULong(), msgType = when (record.chatType) {
memberCard = record.sendMemberName, MsgConstant.KCHATTYPEC2C -> 9
u1 = 2 MsgConstant.KCHATTYPEGROUP -> 82
) else null else -> throw UnsupportedOperationException("Unsupported chatType: ${contact.chatType}")
), },
contentHead = ContentHead( msgSubType = if (record.chatType == MsgConstant.KCHATTYPEC2C) 175 else null,
msgType = when (record.chatType) { divSeq = if (record.chatType == MsgConstant.KCHATTYPEC2C) 175 else null,
MsgConstant.KCHATTYPEC2C -> 9 msgViaRandom = record.msgId,
MsgConstant.KCHATTYPEGROUP -> 82 sequence = record.msgSeq, // idk what this is(i++)
else -> throw UnsupportedOperationException("Unsupported chatType: $chatType") msgTime = record.msgTime,
}, u2 = 1,
msgSubType = if (record.chatType == MsgConstant.KCHATTYPEC2C) 175 else null, u6 = 0,
divSeq = if (record.chatType == MsgConstant.KCHATTYPEC2C) 175 else null, u7 = 0,
msgViaRandom = record.msgId, msgSeq = if (record.chatType == MsgConstant.KCHATTYPEC2C) record.msgSeq else null, // seq for dm
sequence = record.msgSeq, // idk what this is(i++) forwardHead = ForwardHead(
msgTime = record.msgTime, u1 = 0,
u2 = 1, u2 = 0,
u6 = 0, u3 = 0,
u7 = 0, ub641 = "",
msgSeq = if (record.chatType == MsgConstant.KCHATTYPEC2C) record.msgSeq else null, // seq for dm avatar = ""
forwardHead = ForwardHead( )
u1 = 0, ),
u2 = 0, body = MsgBody(
u3 = 0, richText = record.elements.toKritorReqMessages(contact).toRichText(contact).onFailure {
ub641 = "", error("消息合成失败: ${it.stackTraceToString()}")
avatar = "" }.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( ForwardMessageBody.ForwardMessageCase.MESSAGE -> {
msgHead = ResponseHead( val _msg = msg.message
peer = msg.senderOrNull?.uin ?: TicketHelper.getUin().toLong(), PushMsgBody(
peerUid = msg.senderOrNull?.uid ?: TicketHelper.getUid(), msgHead = if (_msg.hasSender()) ResponseHead(
receiverUid = TicketHelper.getUid(), peer = if (_msg.sender.hasUin()) _msg.sender.uin else TicketHelper.getUin().toLong(),
forward = ResponseForward( peerUid = _msg.sender.uid,
friendName = msg.senderOrNull?.nick ?: TicketHelper.getNickname() 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
) )
),
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 = ""
)
),
body = MsgBody(
richText = msg.elementsList.toRichText(contact).onSuccess {
desc[++i] = (msg.senderOrNull?.nick ?: TicketHelper.getNickname()) + ": " + it.first
}.onFailure {
error("消息合成失败: ${it.stackTraceToString()}")
}.getOrThrow().second
) )
) }
else -> null
} }
}.onFailure { }.onFailure {
LogCenter.log("消息节点解析失败:${it.stackTraceToString()}", Level.WARN) LogCenter.log("消息节点解析失败:${it.stackTraceToString()}", Level.WARN)
@ -186,19 +185,21 @@ internal object ForwardMessageHelper: QQInterfaces() {
) )
val req = LongMsgReq( val req = LongMsgReq(
sendInfo = when (chatType) { sendInfo = when (contact.chatType) {
MsgConstant.KCHATTYPEC2C -> SendLongMsgInfo( MsgConstant.KCHATTYPEC2C -> SendLongMsgInfo(
type = 1, type = 1,
uid = LongMsgUid(if(peerId.startsWith("u_")) peerId else ContactHelper.getUidByUinAsync(peerId.toLong()) ), uid = LongMsgUid(contact.peerUid),
payload = DeflateTools.gzip(payload.toByteArray()) payload = DeflateTools.gzip(payload.toByteArray())
) )
MsgConstant.KCHATTYPEGROUP -> SendLongMsgInfo( MsgConstant.KCHATTYPEGROUP -> SendLongMsgInfo(
type = 3, type = 3,
uid = LongMsgUid(fromId), uid = LongMsgUid(contact.peerUid),
groupUin = fromId.toULong(), groupUin = contact.peerUid.toULong(),
payload = DeflateTools.gzip(payload.toByteArray()) payload = DeflateTools.gzip(payload.toByteArray())
) )
else -> throw UnsupportedOperationException("Unsupported chatType: $chatType")
else -> throw UnsupportedOperationException("Unsupported chatType: ${contact.chatType}")
}, },
setting = LongMsgSettings( setting = LongMsgSettings(
field1 = 4, field1 = 4,
@ -208,8 +209,9 @@ internal object ForwardMessageHelper: QQInterfaces() {
) )
).toByteArray() ).toByteArray()
val fromServiceMsg = sendBufferAW("trpc.group.long_msg_interface.MsgService.SsoSendLongMsg", true, req, timeout = 60.seconds) val fromServiceMsg =
?: return Result.failure(Exception("unable to upload multi message, response timeout")) sendBufferAW("trpc.group.long_msg_interface.MsgService.SsoSendLongMsg", true, req, timeout = 60.seconds)
?: return Result.failure(Exception("unable to upload multi message, response timeout"))
val rsp = runCatching { val rsp = runCatching {
fromServiceMsg.wupBuffer.slice(4).decodeProtobuf<LongMsgRsp>() fromServiceMsg.wupBuffer.slice(4).decodeProtobuf<LongMsgRsp>()
}.getOrElse { }.getOrElse {
@ -217,11 +219,11 @@ internal object ForwardMessageHelper: QQInterfaces() {
} }
val resId = rsp.sendResult?.resId ?: return Result.failure(Exception("unable to upload multi message")) val resId = rsp.sendResult?.resId ?: return Result.failure(Exception("unable to upload multi message"))
return Result.success(forwardElement { return Result.success(ForwardElement.newBuilder().apply {
this.id = resId this.resId = resId
this.summary = summary this.summary = summary
this.uniseq = UUID.randomUUID().toString() this.uniseq = UUID.randomUUID().toString()
this.description = desc.slice(0..if (i < 3) i else 3).joinToString("\n") this.description = desc.slice(0..if (i < 3) i else 3).joinToString("\n")
}) }.build())
} }
} }

View File

@ -1,31 +1,13 @@
package qq.service.msg package qq.service.msg
import com.google.protobuf.ByteString
import com.tencent.mobileqq.qroute.QRoute import com.tencent.mobileqq.qroute.QRoute
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MsgElement import com.tencent.qqnt.kernel.nativeinterface.MsgElement
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
import com.tencent.qqnt.msg.api.IMsgService import com.tencent.qqnt.msg.api.IMsgService
import io.kritor.event.Element import io.kritor.common.*
import io.kritor.event.ImageType import io.kritor.common.Element.ElementType
import io.kritor.event.Scene
import io.kritor.event.atElement
import io.kritor.event.basketballElement
import io.kritor.event.buttonAction
import io.kritor.event.buttonActionPermission
import io.kritor.event.buttonRender
import io.kritor.event.contactElement
import io.kritor.event.diceElement
import io.kritor.event.faceElement
import io.kritor.event.forwardElement
import io.kritor.event.imageElement
import io.kritor.event.jsonElement
import io.kritor.event.locationElement
import io.kritor.event.pokeElement
import io.kritor.event.replyElement
import io.kritor.event.rpsElement
import io.kritor.event.textElement
import io.kritor.event.videoElement
import io.kritor.event.voiceElement
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.shamrock.helper.ActionMsgException import moe.fuqiuluo.shamrock.helper.ActionMsgException
@ -75,12 +57,14 @@ private object MsgConvertor {
val text = element.textElement val text = element.textElement
val elem = Element.newBuilder() val elem = Element.newBuilder()
if (text.atType != MsgConstant.ATTYPEUNKNOWN) { if (text.atType != MsgConstant.ATTYPEUNKNOWN) {
elem.setAt(atElement { elem.type = ElementType.AT
elem.setAt(AtElement.newBuilder().apply {
this.uid = text.atNtUid this.uid = text.atNtUid
this.uin = ContactHelper.getUinByUidAsync(text.atNtUid).toLong() this.uin = ContactHelper.getUinByUidAsync(text.atNtUid).toLong()
}) })
} else { } else {
elem.setText(textElement { elem.type = ElementType.TEXT
elem.setText(TextElement.newBuilder().apply {
this.text = text.content this.text = text.content
}) })
} }
@ -91,31 +75,51 @@ private object MsgConvertor {
val face = element.faceElement val face = element.faceElement
val elem = Element.newBuilder() val elem = Element.newBuilder()
if (face.faceType == 5) { if (face.faceType == 5) {
elem.setPoke(pokeElement { elem.type = ElementType.POKE
elem.setPoke(PokeElement.newBuilder().apply {
this.id = face.vaspokeId this.id = face.vaspokeId
this.type = face.pokeType this.type = face.pokeType
this.strength = face.pokeStrength this.strength = face.pokeStrength
}) })
} else { } else {
when(face.faceIndex) { when (face.faceIndex) {
114 -> elem.setBasketball(basketballElement { 114 -> {
this.id = face.resultId.ifNullOrEmpty { "0" }?.toInt() ?: 0 elem.type = ElementType.BASKETBALL
}) elem.setBasketball(BasketballElement.newBuilder().apply {
358 -> elem.setDice(diceElement { this.id = face.resultId.ifNullOrEmpty { "0" }?.toInt() ?: 0
this.id = face.resultId.ifNullOrEmpty { "0" }?.toInt() ?: 0 })
}) }
359 -> elem.setRps(rpsElement {
this.id = face.resultId.ifNullOrEmpty { "0" }?.toInt() ?: 0 358 -> {
}) elem.type = ElementType.DICE
394 -> elem.setFace(faceElement { elem.setDice(DiceElement.newBuilder().apply {
this.id = face.faceIndex this.id = face.resultId.ifNullOrEmpty { "0" }?.toInt() ?: 0
this.isBig = face.faceType == 3 })
this.result = face.resultId.ifNullOrEmpty { "1" }?.toInt() ?: 1 }
})
else -> elem.setFace(faceElement { 359 -> {
this.id = face.faceIndex elem.type = ElementType.RPS
this.isBig = face.faceType == 3 elem.setRps(RpsElement.newBuilder().apply {
}) this.id = face.resultId.ifNullOrEmpty { "0" }?.toInt() ?: 0
})
}
394 -> {
elem.type = ElementType.FACE
elem.setFace(FaceElement.newBuilder().apply {
this.id = face.faceIndex
this.isBig = face.faceType == 3
this.result = face.resultId.ifNullOrEmpty { "1" }?.toInt() ?: 1
})
}
else -> {
elem.type = ElementType.FACE
elem.setFace(FaceElement.newBuilder().apply {
this.id = face.faceIndex
this.isBig = face.faceType == 3
})
}
} }
} }
return Result.success(elem.build()) return Result.success(elem.build())
@ -150,9 +154,10 @@ private object MsgConvertor {
LogCenter.log({ "receive image: $image" }, Level.DEBUG) LogCenter.log({ "receive image: $image" }, Level.DEBUG)
val elem = Element.newBuilder() val elem = Element.newBuilder()
elem.setImage(imageElement { elem.type = ElementType.IMAGE
this.file = md5 elem.setImage(ImageElement.newBuilder().apply {
this.url = when (record.chatType) { this.file = ByteString.copyFromUtf8(md5)
this.fileUrl = when (record.chatType) {
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl( MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(
originalUrl = originalUrl, originalUrl = originalUrl,
md5 = md5, md5 = md5,
@ -190,7 +195,8 @@ private object MsgConvertor {
else -> throw UnsupportedOperationException("Not supported chat type: ${record.chatType}") 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 this.type =
if (image.isFlashPic == true) ImageElement.ImageType.FLASH else if (image.original) ImageElement.ImageType.ORIGIN else ImageElement.ImageType.COMMON
this.subType = image.picSubType this.subType = image.picSubType
}) })
@ -205,14 +211,19 @@ private object MsgConvertor {
ptt.fileName.substring(5) ptt.fileName.substring(5)
else ptt.md5HexStr else ptt.md5HexStr
elem.setVoice(voiceElement { elem.type = ElementType.VOICE
this.url = when (record.chatType) { elem.setVoice(VoiceElement.newBuilder().apply {
this.fileUrl = when (record.chatType) {
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPttDownUrl("0", ptt.fileUuid) MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPttDownUrl("0", ptt.fileUuid)
MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupPttDownUrl("0", md5.hex2ByteArray(), ptt.fileUuid) MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupPttDownUrl(
"0",
md5.hex2ByteArray(),
ptt.fileUuid
)
else -> throw UnsupportedOperationException("Not supported chat type: ${record.chatType}") else -> throw UnsupportedOperationException("Not supported chat type: ${record.chatType}")
} }
this.file = md5 this.file = ByteString.copyFromUtf8(md5)
this.magic = ptt.voiceChangeType != MsgConstant.KPTTVOICECHANGETYPENONE this.magic = ptt.voiceChangeType != MsgConstant.KPTTVOICECHANGETYPENONE
}) })
@ -229,9 +240,10 @@ private object MsgConvertor {
it[it.size - 2].hex2ByteArray() it[it.size - 2].hex2ByteArray()
} }
} else video.fileName.split(".")[0].hex2ByteArray() } else video.fileName.split(".")[0].hex2ByteArray()
elem.setVideo(videoElement { elem.type = ElementType.VIDEO
this.file = md5.toHexString() elem.setVideo(VideoElement.newBuilder().apply {
this.url = when (record.chatType) { this.file = ByteString.copyFromUtf8(md5.toHexString())
this.fileUrl = when (record.chatType) {
MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid) MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid)
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", md5, video.fileUuid) MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", md5, video.fileUuid)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid) MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid)
@ -244,7 +256,8 @@ private object MsgConvertor {
suspend fun convertMarketFace(record: MsgRecord, element: MsgElement): Result<Element> { suspend fun convertMarketFace(record: MsgRecord, element: MsgElement): Result<Element> {
val marketFace = element.marketFaceElement val marketFace = element.marketFaceElement
val elem = Element.newBuilder() val elem = Element.newBuilder()
elem.setMarketFace(io.kritor.event.marketFaceElement { elem.type = ElementType.MARKET_FACE
elem.setMarketFace(MarketFaceElement.newBuilder().apply {
this.id = marketFace.emojiId.lowercase() this.id = marketFace.emojiId.lowercase()
}) })
return Result.success(elem.build()) return Result.success(elem.build())
@ -256,8 +269,9 @@ private object MsgConvertor {
when (data["app"].asString) { when (data["app"].asString) {
"com.tencent.multimsg" -> { "com.tencent.multimsg" -> {
val info = data["meta"].asJsonObject["detail"].asJsonObject val info = data["meta"].asJsonObject["detail"].asJsonObject
elem.setForward(forwardElement { elem.type = ElementType.FORWARD
this.id = info["resid"].asString elem.setForward(ForwardElement.newBuilder().apply {
this.resId = info["resid"].asString
this.uniseq = info["uniseq"].asString this.uniseq = info["uniseq"].asString
this.summary = info["summary"].asString this.summary = info["summary"].asString
this.description = info["news"].asJsonArray.joinToString("\n") { this.description = info["news"].asJsonArray.joinToString("\n") {
@ -268,7 +282,8 @@ private object MsgConvertor {
"com.tencent.troopsharecard" -> { "com.tencent.troopsharecard" -> {
val info = data["meta"].asJsonObject["contact"].asJsonObject val info = data["meta"].asJsonObject["contact"].asJsonObject
elem.setContact(contactElement { elem.type = ElementType.CONTACT
elem.setContact(ContactElement.newBuilder().apply {
this.scene = Scene.GROUP this.scene = Scene.GROUP
this.peer = info["jumpUrl"].asString.split("group_code=")[1] this.peer = info["jumpUrl"].asString.split("group_code=")[1]
}) })
@ -276,7 +291,8 @@ private object MsgConvertor {
"com.tencent.contact.lua" -> { "com.tencent.contact.lua" -> {
val info = data["meta"].asJsonObject["contact"].asJsonObject val info = data["meta"].asJsonObject["contact"].asJsonObject
elem.setContact(contactElement { elem.type = ElementType.CONTACT
elem.setContact(ContactElement.newBuilder().apply {
this.scene = Scene.FRIEND this.scene = Scene.FRIEND
this.peer = info["jumpUrl"].asString.split("uin=")[1] this.peer = info["jumpUrl"].asString.split("uin=")[1]
}) })
@ -284,7 +300,8 @@ private object MsgConvertor {
"com.tencent.map" -> { "com.tencent.map" -> {
val info = data["meta"].asJsonObject["Location.Search"].asJsonObject val info = data["meta"].asJsonObject["Location.Search"].asJsonObject
elem.setLocation(locationElement { elem.type = ElementType.LOCATION
elem.setLocation(LocationElement.newBuilder().apply {
this.lat = info["lat"].asString.toFloat() this.lat = info["lat"].asString.toFloat()
this.lon = info["lng"].asString.toFloat() this.lon = info["lng"].asString.toFloat()
this.address = info["address"].asString this.address = info["address"].asString
@ -292,9 +309,12 @@ private object MsgConvertor {
}) })
} }
else -> elem.setJson(jsonElement { else -> {
this.json = data.toString() elem.type = ElementType.JSON
}) elem.setJson(JsonElement.newBuilder().apply {
this.json = data.toString()
})
}
} }
return Result.success(elem.build()) return Result.success(elem.build())
} }
@ -302,21 +322,23 @@ private object MsgConvertor {
suspend fun convertReply(record: MsgRecord, element: MsgElement): Result<Element> { suspend fun convertReply(record: MsgRecord, element: MsgElement): Result<Element> {
val reply = element.replyElement val reply = element.replyElement
val elem = Element.newBuilder() val elem = Element.newBuilder()
elem.setReply(replyElement { elem.type = ElementType.REPLY
elem.setReply(ReplyElement.newBuilder().apply {
val msgSeq = reply.replayMsgSeq val msgSeq = reply.replayMsgSeq
val contact = MessageHelper.generateContact(record) val contact = MessageHelper.generateContact(record)
val sourceRecords = withTimeoutOrNull(3000) { val sourceRecords = withTimeoutOrNull(3000) {
suspendCancellableCoroutine { suspendCancellableCoroutine {
QRoute.api(IMsgService::class.java).getMsgsBySeqAndCount(contact, msgSeq, 1, true) { _, _, records -> QRoute.api(IMsgService::class.java)
it.resume(records) .getMsgsBySeqAndCount(contact, msgSeq, 1, true) { _, _, records ->
} it.resume(records)
}
} }
} }
if (sourceRecords.isNullOrEmpty()) { if (sourceRecords.isNullOrEmpty()) {
LogCenter.log("无法查询到回复的消息ID: seq = $msgSeq", Level.WARN) LogCenter.log("无法查询到回复的消息ID: seq = $msgSeq", Level.WARN)
this.messageId = reply.replayMsgId this.messageId = reply.replayMsgId.toString()
} else { } else {
this.messageId = sourceRecords.first().msgId this.messageId = sourceRecords.first().msgId.toString()
} }
}) })
return Result.success(elem.build()) return Result.success(elem.build())
@ -332,11 +354,18 @@ private object MsgConvertor {
val fileSubId = fileMsg.fileSubId ?: "" val fileSubId = fileMsg.fileSubId ?: ""
val url = when (record.chatType) { val url = when (record.chatType) {
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId) MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildFileDownUrl(record.guildId, record.channelId, fileId, bizId) MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildFileDownUrl(
record.guildId,
record.channelId,
fileId,
bizId
)
else -> RichProtoSvc.getGroupFileDownUrl(record.peerUin, fileId, bizId) else -> RichProtoSvc.getGroupFileDownUrl(record.peerUin, fileId, bizId)
} }
val elem = Element.newBuilder() val elem = Element.newBuilder()
elem.setFile(io.kritor.event.fileElement { elem.type = ElementType.FILE
elem.setFile(FileElement.newBuilder().apply {
this.name = fileName this.name = fileName
this.size = fileSize this.size = fileSize
this.url = url this.url = url
@ -351,7 +380,8 @@ private object MsgConvertor {
suspend fun convertMarkdown(record: MsgRecord, element: MsgElement): Result<Element> { suspend fun convertMarkdown(record: MsgRecord, element: MsgElement): Result<Element> {
val markdown = element.markdownElement val markdown = element.markdownElement
val elem = Element.newBuilder() val elem = Element.newBuilder()
elem.setMarkdown(io.kritor.event.markdownElement { elem.type = ElementType.MARKDOWN
elem.setMarkdown(MarkdownElement.newBuilder().apply {
this.markdown = markdown.content this.markdown = markdown.content
}) })
return Result.success(elem.build()) return Result.success(elem.build())
@ -360,7 +390,8 @@ private object MsgConvertor {
suspend fun convertBubbleFace(record: MsgRecord, element: MsgElement): Result<Element> { suspend fun convertBubbleFace(record: MsgRecord, element: MsgElement): Result<Element> {
val bubbleFace = element.faceBubbleElement val bubbleFace = element.faceBubbleElement
val elem = Element.newBuilder() val elem = Element.newBuilder()
elem.setBubbleFace(io.kritor.event.bubbleFaceElement { elem.type = ElementType.BUBBLE_FACE
elem.setBubbleFace(BubbleFaceElement.newBuilder().apply {
this.id = bubbleFace.yellowFaceInfo.index this.id = bubbleFace.yellowFaceInfo.index
this.count = bubbleFace.faceCount ?: 1 this.count = bubbleFace.faceCount ?: 1
}) })
@ -370,34 +401,35 @@ private object MsgConvertor {
suspend fun convertInlineKeyboard(record: MsgRecord, element: MsgElement): Result<Element> { suspend fun convertInlineKeyboard(record: MsgRecord, element: MsgElement): Result<Element> {
val inlineKeyboard = element.inlineKeyboardElement val inlineKeyboard = element.inlineKeyboardElement
val elem = Element.newBuilder() val elem = Element.newBuilder()
elem.setButton(io.kritor.event.buttonElement { elem.type = ElementType.BUTTON
elem.setButton(ButtonElement.newBuilder().apply {
inlineKeyboard.rows.forEach { row -> inlineKeyboard.rows.forEach { row ->
this.rows.add(io.kritor.event.row { this.addRows(ButtonRow.newBuilder().apply {
row.buttons.forEach buttonsLoop@ { button -> row.buttons.forEach buttonsLoop@{ button ->
if (button == null) return@buttonsLoop if (button == null) return@buttonsLoop
this.buttons.add(io.kritor.event.button { this.addButtons(Button.newBuilder().apply {
this.id = button.id this.id = button.id
this.action = buttonAction { this.action = ButtonAction.newBuilder().apply {
this.type = button.type this.type = button.type
this.permission = buttonActionPermission { this.permission = ButtonActionPermission.newBuilder().apply {
this.type = button.permissionType this.type = button.permissionType
button.specifyRoleIds?.let { button.specifyRoleIds?.let {
this.roleIds.addAll(it) this.addAllRoleIds(it)
} }
button.specifyTinyids?.let { button.specifyTinyids?.let {
this.userIds.addAll(it) this.addAllUserIds(it)
} }
} }.build()
this.unsupportedTips = button.unsupportTips ?: "" this.unsupportedTips = button.unsupportTips ?: ""
this.data = button.data ?: "" this.data = button.data ?: ""
this.reply = button.isReply this.reply = button.isReply
this.enter = button.enter this.enter = button.enter
} }.build()
this.renderData = buttonRender { this.renderData = ButtonRender.newBuilder().apply {
this.label = button.label ?: "" this.label = button.label ?: ""
this.visitedLabel = button.visitedLabel ?: "" this.visitedLabel = button.visitedLabel ?: ""
this.style = button.style this.style = button.style
} }.build()
}) })
} }
}) })

View File

@ -1,24 +1,12 @@
@file:OptIn(ExperimentalUnsignedTypes::class) @file:OptIn(ExperimentalUnsignedTypes::class)
package qq.service.msg package qq.service.msg
import com.tencent.qqnt.kernel.nativeinterface.Contact import com.tencent.qqnt.kernel.nativeinterface.Contact
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import io.kritor.message.Element import io.kritor.common.*
import io.kritor.message.ElementType import io.kritor.common.Element.ElementType
import io.kritor.message.ImageType import io.kritor.common.ImageElement.ImageType
import io.kritor.message.Scene
import io.kritor.message.atElement
import io.kritor.message.buttonActionPermission
import io.kritor.message.buttonElement
import io.kritor.message.contactElement
import io.kritor.message.faceElement
import io.kritor.message.forwardElement
import io.kritor.message.imageElement
import io.kritor.message.jsonElement
import io.kritor.message.locationElement
import io.kritor.message.markdownElement
import io.kritor.message.replyElement
import io.kritor.message.textElement
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import kotlinx.io.core.readUInt import kotlinx.io.core.readUInt
@ -48,142 +36,151 @@ suspend fun List<Elem>.toKritorResponseMessages(contact: Contact): ArrayList<Ele
val at = ByteReadPacket(text.attr6Buf!!) val at = ByteReadPacket(text.attr6Buf!!)
at.discardExact(7) at.discardExact(7)
val uin = at.readUInt() val uin = at.readUInt()
kritorMessages.add(io.kritor.message.element { kritorMessages.add(Element.newBuilder().apply {
this.type = ElementType.AT this.type = ElementType.AT
this.at = atElement { this.at = AtElement.newBuilder().apply {
this.uin = uin.toLong() this.uin = uin.toLong()
} }.build()
}) }.build())
} else { } else {
kritorMessages.add(io.kritor.message.element { kritorMessages.add(Element.newBuilder().apply {
this.type = ElementType.TEXT this.type = ElementType.TEXT
this.text = textElement { this.text = TextElement.newBuilder().apply {
this.text = text.str ?: "" this.text = text.str ?: ""
} }.build()
}) }.build())
} }
} else if (element.face != null) { } else if (element.face != null) {
kritorMessages.add(io.kritor.message.element { kritorMessages.add(Element.newBuilder().apply {
this.type = ElementType.FACE this.type = ElementType.FACE
this.face = faceElement { this.face = FaceElement.newBuilder().apply {
this.id = element.face!!.index ?: 0 this.id = element.face!!.index ?: 0
} }.build()
}) }.build())
} else if (element.customFace != null) { } else if (element.customFace != null) {
val customFace = element.customFace!! val customFace = element.customFace!!
val md5 = customFace.md5.toHexString() val md5 = customFace.md5.toHexString()
val origUrl = customFace.origUrl!! val origUrl = customFace.origUrl!!
kritorMessages.add(io.kritor.message.element { kritorMessages.add(Element.newBuilder().apply {
this.type = ElementType.IMAGE this.type = ElementType.IMAGE
this.image = imageElement { this.image = ImageElement.newBuilder().apply {
this.fileName = md5 this.fileMd5 = md5
this.type = if (customFace.origin == true) ImageType.ORIGIN else ImageType.COMMON this.type = if (customFace.origin == true) ImageType.ORIGIN else ImageType.COMMON
this.url = when (contact.chatType) { this.fileUrl = when (contact.chatType) {
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(origUrl, md5) MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(
origUrl,
md5
)
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(origUrl, md5) MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(origUrl, md5)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(origUrl, md5) MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(origUrl, md5)
else -> throw UnsupportedOperationException("Not supported chat type: $contact") else -> throw UnsupportedOperationException("Not supported chat type: $contact")
} }
} }.build()
}) }.build())
} else if (element.notOnlineImage != null) { } else if (element.notOnlineImage != null) {
require(element.notOnlineImage != null)
val md5 = element.notOnlineImage!!.picMd5.toHexString() val md5 = element.notOnlineImage!!.picMd5.toHexString()
val origUrl = element.notOnlineImage!!.origUrl!! val origUrl = element.notOnlineImage!!.origUrl!!
kritorMessages.add(io.kritor.message.element { kritorMessages.add(Element.newBuilder().apply {
this.type = ElementType.IMAGE this.type = ElementType.IMAGE
this.image = imageElement { this.image = ImageElement.newBuilder().apply {
this.fileName = md5 this.fileMd5 = md5
this.type = if (element.notOnlineImage?.original == true) ImageType.ORIGIN else ImageType.COMMON this.type = if (element.notOnlineImage?.original == true) ImageType.ORIGIN else ImageType.COMMON
this.url = when (contact.chatType) { this.fileUrl = when (contact.chatType) {
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(origUrl, md5) MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(
origUrl,
md5
)
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(origUrl, md5) MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(origUrl, md5)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(origUrl, md5) MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(origUrl, md5)
else -> throw UnsupportedOperationException("Not supported chat type: $contact") else -> throw UnsupportedOperationException("Not supported chat type: $contact")
} }
} }.build()
}) }.build())
} else if (element.generalFlags != null) { } else if (element.generalFlags != null) {
val generalFlags = element.generalFlags!! // val generalFlags = element.generalFlags!!
if (generalFlags.longTextFlag == 1u) { // if (generalFlags.longTextFlag == 1u) {
kritorMessages.add(io.kritor.message.element { // kritorMessages.add(Element.newBuilder().apply {
this.type = ElementType.FORWARD // this.type = ElementType.FORWARD
this.forward = forwardElement { // this.forward = forwardElement {
this.id = generalFlags.longTextResid ?: "" // this.id = generalFlags.longTextResid ?: ""
} // }
}) // })
} // }
} else if (element.srcMsg != null) { } else if (element.srcMsg != null) {
val srcMsg = element.srcMsg!! val srcMsg = element.srcMsg!!
val msgId = srcMsg.pbReserve?.msgRand?.toLong() ?: 0 val msgId = srcMsg.pbReserve?.msgRand?.toLong() ?: 0
kritorMessages.add(io.kritor.message.element { kritorMessages.add(Element.newBuilder().apply {
this.type = ElementType.REPLY this.type = ElementType.REPLY
this.reply = replyElement { this.reply = ReplyElement.newBuilder().apply {
this.messageId = msgId this.messageId = msgId.toString()
} }.build()
}) }.build())
} else if (element.lightApp != null) { } else if (element.lightApp != null) {
val data = element.lightApp!!.data!! val data = element.lightApp!!.data!!
val jsonStr = (if (data[0].toInt() == 1) DeflateTools.uncompress(data.slice(1)) else data.slice(1)).decodeToString() val jsonStr =
(if (data[0].toInt() == 1) DeflateTools.uncompress(data.slice(1)) else data.slice(1)).decodeToString()
val json = jsonStr.asJsonObject val json = jsonStr.asJsonObject
when (json["app"].asString) { when (json["app"].asString) {
"com.tencent.multimsg" -> { "com.tencent.multimsg" -> {
val info = json["meta"].asJsonObject["detail"].asJsonObject val info = json["meta"].asJsonObject["detail"].asJsonObject
kritorMessages.add(io.kritor.message.element { kritorMessages.add(Element.newBuilder().apply {
this.type = ElementType.FORWARD this.type = ElementType.FORWARD
this.forward = forwardElement { this.forward = ForwardElement.newBuilder().apply {
this.id = info["resid"].asString this.resId = info["resid"].asString
this.uniseq = info["uniseq"].asString this.uniseq = info["uniseq"].asString
this.summary = info["summary"].asString this.summary = info["summary"].asString
this.description = info["news"].asJsonArray.joinToString("\n") { this.description = info["news"].asJsonArray.joinToString("\n") {
it.asJsonObject["text"].asString it.asJsonObject["text"].asString
} }
} }.build()
}) }.build())
} }
"com.tencent.troopsharecard" -> { "com.tencent.troopsharecard" -> {
val info = json["meta"].asJsonObject["contact"].asJsonObject val info = json["meta"].asJsonObject["contact"].asJsonObject
kritorMessages.add(io.kritor.message.element { kritorMessages.add(Element.newBuilder().apply {
this.type = ElementType.CONTACT this.type = ElementType.CONTACT
this.contact = contactElement { this.contact = ContactElement.newBuilder().apply {
this.scene = Scene.GROUP this.scene = Scene.GROUP
this.peer = info["jumpUrl"].asString.split("group_code=")[1] this.peer = info["jumpUrl"].asString.split("group_code=")[1]
} }.build()
}) }.build())
} }
"com.tencent.contact.lua" -> { "com.tencent.contact.lua" -> {
val info = json["meta"].asJsonObject["contact"].asJsonObject val info = json["meta"].asJsonObject["contact"].asJsonObject
kritorMessages.add(io.kritor.message.element { kritorMessages.add(Element.newBuilder().apply {
this.type = ElementType.CONTACT this.type = ElementType.CONTACT
this.contact = contactElement { this.contact = ContactElement.newBuilder().apply {
this.scene = Scene.FRIEND this.scene = Scene.FRIEND
this.peer = info["jumpUrl"].asString.split("uin=")[1] this.peer = info["jumpUrl"].asString.split("uin=")[1]
} }.build()
}) }.build())
} }
"com.tencent.map" -> { "com.tencent.map" -> {
val info = json["meta"].asJsonObject["Location.Search"].asJsonObject val info = json["meta"].asJsonObject["Location.Search"].asJsonObject
kritorMessages.add(io.kritor.message.element { kritorMessages.add(Element.newBuilder().apply {
this.type = ElementType.LOCATION this.type = ElementType.LOCATION
this.location = locationElement { this.location = LocationElement.newBuilder().apply {
this.lat = info["lat"].asString.toFloat() this.lat = info["lat"].asString.toFloat()
this.lon = info["lng"].asString.toFloat() this.lon = info["lng"].asString.toFloat()
this.address = info["address"].asString this.address = info["address"].asString
this.title = info["name"].asString this.title = info["name"].asString
} }.build()
}) }.build())
} }
else -> { else -> {
kritorMessages.add(io.kritor.message.element { kritorMessages.add(Element.newBuilder().apply {
this.type = ElementType.JSON this.type = ElementType.JSON
this.json = jsonElement { this.json = JsonElement.newBuilder().apply {
this.json = jsonStr this.json = jsonStr
} }.build()
}) }.build())
} }
} }
} else if (element.commonElem != null) { } else if (element.commonElem != null) {
@ -192,81 +189,78 @@ suspend fun List<Elem>.toKritorResponseMessages(contact: Contact): ArrayList<Ele
37 -> { 37 -> {
val qFaceExtra = commonElem.elem!!.decodeProtobuf<QFaceExtra>() val qFaceExtra = commonElem.elem!!.decodeProtobuf<QFaceExtra>()
when (qFaceExtra.faceId) { when (qFaceExtra.faceId) {
358 -> kritorMessages.add(io.kritor.message.element { 358 -> kritorMessages.add(Element.newBuilder().apply {
this.type = ElementType.DICE this.type = ElementType.DICE
this.dice = io.kritor.message.diceElement { this.dice = DiceElement.newBuilder().apply {
this.id = qFaceExtra.result!!.toInt() this.id = qFaceExtra.result!!.toInt()
} }.build()
}) }.build())
359 -> kritorMessages.add(io.kritor.message.element { 359 -> kritorMessages.add(Element.newBuilder().apply {
this.type = ElementType.RPS this.type = ElementType.RPS
this.rps = io.kritor.message.rpsElement { this.rps = RpsElement.newBuilder().apply {
this.id = qFaceExtra.result!!.toInt() this.id = qFaceExtra.result!!.toInt()
} }.build()
}) }.build())
else -> kritorMessages.add(io.kritor.message.element { else -> kritorMessages.add(Element.newBuilder().apply {
this.type = ElementType.FACE this.type = ElementType.FACE
this.face = faceElement { this.face = FaceElement.newBuilder().apply {
this.id = qFaceExtra.faceId ?: 0 this.id = qFaceExtra.faceId ?: 0
this.isBig = false this.isBig = false
this.result = qFaceExtra.result?.toInt() ?: 0 this.result = qFaceExtra.result?.toInt() ?: 0
} }.build()
}) }.build())
} }
} }
45 -> { 45 -> {
val markdownExtra = commonElem.elem!!.decodeProtobuf<MarkdownExtra>() val markdownExtra = commonElem.elem!!.decodeProtobuf<MarkdownExtra>()
kritorMessages.add(io.kritor.message.element { kritorMessages.add(Element.newBuilder().apply {
this.type = ElementType.MARKDOWN this.type = ElementType.MARKDOWN
this.markdown = markdownElement { this.markdown = MarkdownElement.newBuilder().apply {
this.markdown = markdownExtra.content!! this.markdown = markdownExtra.content!!
} }.build()
}) }.build())
} }
46 -> { 46 -> {
val buttonExtra = commonElem.elem!!.decodeProtobuf<ButtonExtra>() val buttonExtra = commonElem.elem!!.decodeProtobuf<ButtonExtra>()
kritorMessages.add(io.kritor.message.element { kritorMessages.add(
this.type = ElementType.BUTTON Element.newBuilder().setButton(ButtonElement.newBuilder().apply {
this.button = buttonElement { this.addAllRows(buttonExtra.field1!!.rows!!.map { row ->
buttonExtra.field1!!.rows?.forEach { row -> ButtonRow.newBuilder().apply {
this.rows.add(io.kritor.message.row { this.addAllButtons(row.buttons!!.map { button ->
row.buttons?.forEach { button -> Button.newBuilder().apply {
this.buttons.add(io.kritor.message.button { this.id = button.id
val renderData = button.renderData this.renderData = ButtonRender.newBuilder().apply {
val action = button.action this.label = button.renderData?.label ?: ""
val permission = action?.permission this.visitedLabel = button.renderData?.visitedLabel ?: ""
this.id = button.id ?: "" this.style = button.renderData?.style ?: 0
this.renderData = io.kritor.message.buttonRender { }.build()
this.label = renderData?.label ?: "" this.action = ButtonAction.newBuilder().apply {
this.visitedLabel = renderData?.visitedLabel ?: "" this.type = button.action?.type?:0
this.style = renderData?.style ?: 0 this.permission = ButtonActionPermission.newBuilder().apply {
} this.type = button.action?.permission?.type?:0
this.action = io.kritor.message.buttonAction { button.action?.permission?.specifyRoleIds?.let {
this.type = action?.type ?: 0 this.addAllRoleIds(it)
this.permission = buttonActionPermission { }
this.type = permission?.type ?: 0 button.action?.permission?.specifyUserIds?.let {
this.roleIds.addAll( this.addAllUserIds(it)
permission?.specifyRoleIds ?: emptyList() }
) }.build()
this.userIds.addAll( this.unsupportedTips = button.action?.unsupportTips ?: ""
permission?.specifyUserIds ?: emptyList() this.data = button.action?.data ?: ""
) this.reply = button.action?.reply ?: false
} this.enter = button.action?.enter ?: false
this.unsupportedTips = action?.unsupportTips ?: "" }.build()
this.data = action?.data ?: "" }.build()
this.reply = action?.reply ?: false })
this.enter = action?.enter ?: false }.build()
} })
}) this.botAppid = buttonExtra.field1?.appid?.toLong() ?: 0L
} }.build()).build()
}) )
}
}
})
} }
} }
} }

View File

@ -1,7 +1,6 @@
package qq.service.msg package qq.service.msg
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.util.Base64
import androidx.exifinterface.media.ExifInterface import androidx.exifinterface.media.ExifInterface
import com.tencent.mobileqq.emoticon.QQSysFaceUtil import com.tencent.mobileqq.emoticon.QQSysFaceUtil
import com.tencent.mobileqq.pb.ByteStringMicro import com.tencent.mobileqq.pb.ByteStringMicro
@ -9,17 +8,18 @@ import com.tencent.mobileqq.qroute.QRoute
import com.tencent.qphone.base.remote.ToServiceMsg import com.tencent.qphone.base.remote.ToServiceMsg
import com.tencent.qqnt.aio.adapter.api.IAIOPttApi import com.tencent.qqnt.aio.adapter.api.IAIOPttApi
import com.tencent.qqnt.kernel.nativeinterface.* import com.tencent.qqnt.kernel.nativeinterface.*
import com.tencent.qqnt.kernel.nativeinterface.Contact
import com.tencent.qqnt.kernel.nativeinterface.FaceElement
import com.tencent.qqnt.kernel.nativeinterface.MarkdownElement
import com.tencent.qqnt.kernel.nativeinterface.MarketFaceElement
import com.tencent.qqnt.kernel.nativeinterface.ReplyElement
import com.tencent.qqnt.kernel.nativeinterface.TextElement
import com.tencent.qqnt.msg.api.IMsgService import com.tencent.qqnt.msg.api.IMsgService
import io.kritor.message.AtElement import io.kritor.common.*
import io.kritor.message.Button import io.kritor.common.Element.ElementType
import io.kritor.message.Element import io.kritor.common.ImageElement.ImageType
import io.kritor.message.ElementType import io.kritor.common.MusicElement.MusicPlatform
import io.kritor.message.ElementType.* import io.kritor.common.VideoElement
import io.kritor.message.ImageElement
import io.kritor.message.ImageType
import io.kritor.message.MusicPlatform
import io.kritor.message.Scene
import io.kritor.message.VoiceElement
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.shamrock.config.EnableOldBDH import moe.fuqiuluo.shamrock.config.EnableOldBDH
@ -77,27 +77,27 @@ private typealias NtConvertor = suspend (Contact, Long, Element) -> Result<MsgEl
object NtMsgConvertor { object NtMsgConvertor {
private val ntConvertors = mapOf<ElementType, NtConvertor>( private val ntConvertors = mapOf<ElementType, NtConvertor>(
TEXT to ::textConvertor, ElementType.TEXT to ::textConvertor,
AT to ::atConvertor, ElementType.AT to ::atConvertor,
FACE to ::faceConvertor, ElementType.FACE to ::faceConvertor,
BUBBLE_FACE to ::bubbleFaceConvertor, ElementType.BUBBLE_FACE to ::bubbleFaceConvertor,
REPLY to ::replyConvertor, ElementType.REPLY to ::replyConvertor,
IMAGE to ::imageConvertor, ElementType.IMAGE to ::imageConvertor,
VOICE to ::voiceConvertor, ElementType.VOICE to ::voiceConvertor,
VIDEO to ::videoConvertor, ElementType.VIDEO to ::videoConvertor,
BASKETBALL to ::basketballConvertor, ElementType.BASKETBALL to ::basketballConvertor,
DICE to ::diceConvertor, ElementType.DICE to ::diceConvertor,
RPS to ::rpsConvertor, ElementType.RPS to ::rpsConvertor,
POKE to ::pokeConvertor, ElementType.POKE to ::pokeConvertor,
MUSIC to ::musicConvertor, ElementType.MUSIC to ::musicConvertor,
WEATHER to ::weatherConvertor, ElementType.WEATHER to ::weatherConvertor,
LOCATION to ::locationConvertor, ElementType.LOCATION to ::locationConvertor,
SHARE to ::shareConvertor, ElementType.SHARE to ::shareConvertor,
CONTACT to ::contactConvertor, ElementType.CONTACT to ::contactConvertor,
JSON to ::jsonConvertor, ElementType.JSON to ::jsonConvertor,
FORWARD to ::forwardConvertor, ElementType.FORWARD to ::forwardConvertor,
MARKDOWN to ::markdownConvertor, ElementType.MARKDOWN to ::markdownConvertor,
BUTTON to ::buttonConvertor, ElementType.BUTTON to ::buttonConvertor,
) )
suspend fun convertToNtMsgs(contact: Contact, msgId: Long, msgs: Messages): ArrayList<MsgElement> { suspend fun convertToNtMsgs(contact: Contact, msgId: Long, msgs: Messages): ArrayList<MsgElement> {
@ -135,41 +135,22 @@ object NtMsgConvertor {
val elem = MsgElement() val elem = MsgElement()
val at = TextElement() val at = TextElement()
if (sourceAt.at.accountCase == AtElement.AccountCase.UIN) { val uid = sourceAt.at.uid
val uin = sourceAt.at.uin if (uid == "all" || uid == "0") {
if (uin == 0L) { at.content = "@全体成员"
at.content = "@全体成员" at.atType = MsgConstant.ATTYPEALL
at.atType = MsgConstant.ATTYPEALL at.atNtUid = "0"
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)
}
} else { } else {
val uid = sourceAt.at.uid val uin = ContactHelper.getUinByUidAsync(uid)
if (uid == "all" || uid == "0") { val info = GroupHelper.getTroopMemberInfoByUinV2(contact.peerUid, uin, true).onFailure {
at.content = "@全体成员" LogCenter.log("无法获取群成员信息: contact=$contact, id=${uin}", Level.WARN)
at.atType = MsgConstant.ATTYPEALL }.getOrNull()
at.atNtUid = "0" at.content = "@${
} else { info?.troopnick.ifNullOrEmpty { info?.friendnick }
val uin = ContactHelper.getUinByUidAsync(uid) ?: uin
val info = GroupHelper.getTroopMemberInfoByUinV2(contact.peerUid, uin, true).onFailure { }"
LogCenter.log("无法获取群成员信息: contact=$contact, id=${uin}", Level.WARN) at.atType = MsgConstant.ATTYPEONE
}.getOrNull() at.atNtUid = uid
at.content = "@${
info?.troopnick.ifNullOrEmpty { info?.friendnick }
?: uin
}"
at.atType = MsgConstant.ATTYPEONE
at.atNtUid = uid
}
} }
elem.textElement = at elem.textElement = at
elem.elementType = MsgConstant.KELEMTYPETEXT elem.elementType = MsgConstant.KELEMTYPETEXT
@ -215,7 +196,11 @@ object NtMsgConvertor {
return Result.success(elem) return Result.success(elem)
} }
private suspend fun bubbleFaceConvertor(contact: Contact, msgId: Long, sourceBubbleFace: Element): Result<MsgElement> { private suspend fun bubbleFaceConvertor(
contact: Contact,
msgId: Long,
sourceBubbleFace: Element
): Result<MsgElement> {
val faceId = sourceBubbleFace.bubbleFace.id val faceId = sourceBubbleFace.bubbleFace.id
val local = QQSysFaceUtil.convertToLocal(faceId) val local = QQSysFaceUtil.convertToLocal(faceId)
val name = QQSysFaceUtil.getFaceDescription(local) val name = QQSysFaceUtil.getFaceDescription(local)
@ -242,7 +227,7 @@ object NtMsgConvertor {
element.elementType = MsgConstant.KELEMTYPEREPLY element.elementType = MsgConstant.KELEMTYPEREPLY
val reply = ReplyElement() val reply = ReplyElement()
reply.replayMsgId = sourceReply.reply.messageId reply.replayMsgId = sourceReply.reply.messageId.toLong()
reply.sourceMsgIdInRecords = reply.replayMsgId reply.sourceMsgIdInRecords = reply.replayMsgId
if (reply.replayMsgId == 0L) { if (reply.replayMsgId == 0L) {
@ -251,9 +236,10 @@ object NtMsgConvertor {
withTimeoutOrNull(3000) { withTimeoutOrNull(3000) {
suspendCancellableCoroutine { suspendCancellableCoroutine {
QRoute.api(IMsgService::class.java).getMsgsByMsgId(contact, arrayListOf(reply.replayMsgId)) { _, _, records -> QRoute.api(IMsgService::class.java)
it.resume(records) .getMsgsByMsgId(contact, arrayListOf(reply.replayMsgId)) { _, _, records ->
} it.resume(records)
}
} }
}?.firstOrNull()?.let { }?.firstOrNull()?.let {
reply.replayMsgSeq = it.msgSeq reply.replayMsgSeq = it.msgSeq
@ -270,25 +256,31 @@ object NtMsgConvertor {
private suspend fun imageConvertor(contact: Contact, msgId: Long, sourceImage: Element): Result<MsgElement> { private suspend fun imageConvertor(contact: Contact, msgId: Long, sourceImage: Element): Result<MsgElement> {
val isOriginal = sourceImage.image.type == ImageType.ORIGIN val isOriginal = sourceImage.image.type == ImageType.ORIGIN
val isFlash = sourceImage.image.type == ImageType.FLASH val isFlash = sourceImage.image.type == ImageType.FLASH
val file = when(sourceImage.image.dataCase!!) { val file = when (sourceImage.image.dataCase!!) {
ImageElement.DataCase.FILE_NAME -> { ImageElement.DataCase.FILE_NAME -> {
val fileMd5 = sourceImage.image.fileName.replace(regex = "[{}\\-]".toRegex(), replacement = "").split(".")[0].lowercase() val fileMd5 = sourceImage.image.fileName.replace(regex = "[{}\\-]".toRegex(), replacement = "")
.split(".")[0].lowercase()
FileUtils.getFileByMd5(fileMd5) FileUtils.getFileByMd5(fileMd5)
} }
ImageElement.DataCase.FILE_PATH -> { ImageElement.DataCase.FILE_PATH -> {
val filePath = sourceImage.image.filePath val filePath = sourceImage.image.filePath
File(filePath).inputStream().use { File(filePath).inputStream().use {
FileUtils.saveFileToCache(it) FileUtils.saveFileToCache(it)
} }
} }
ImageElement.DataCase.FILE_BASE64 -> {
FileUtils.saveFileToCache(ByteArrayInputStream( ImageElement.DataCase.FILE -> {
Base64.decode(sourceImage.image.fileBase64, Base64.DEFAULT) FileUtils.saveFileToCache(
)) ByteArrayInputStream(
sourceImage.image.file.toByteArray()
)
)
} }
ImageElement.DataCase.URL -> {
ImageElement.DataCase.FILE_URL -> {
val tmp = FileUtils.getTmpFile() val tmp = FileUtils.getTmpFile()
if(DownloadUtils.download(sourceImage.image.url, tmp)) { if (DownloadUtils.download(sourceImage.image.fileUrl, tmp)) {
tmp.inputStream().use { tmp.inputStream().use {
FileUtils.saveFileToCache(it) FileUtils.saveFileToCache(it)
}.also { }.also {
@ -296,16 +288,20 @@ object NtMsgConvertor {
} }
} else { } else {
tmp.delete() 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")) ImageElement.DataCase.DATA_NOT_SET -> return Result.failure(IllegalArgumentException("ImageElement data is not set"))
} }
if (EnableOldBDH.get()) { if (EnableOldBDH.get()) {
Transfer with when (contact.chatType) { Transfer with when (contact.chatType) {
MsgConstant.KCHATTYPEGROUP -> Troop(contact.peerUid) 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) MsgConstant.KCHATTYPEGUILD -> Troop(contact.peerUid)
else -> return Result.failure(Exception("Not supported chatType(${contact.chatType}) for PictureMsg")) else -> return Result.failure(Exception("Not supported chatType(${contact.chatType}) for PictureMsg"))
} trans PictureResource(file) } trans PictureResource(file)
@ -360,25 +356,29 @@ object NtMsgConvertor {
} }
private suspend fun voiceConvertor(contact: Contact, msgId: Long, sourceVoice: Element): Result<MsgElement> { private suspend fun voiceConvertor(contact: Contact, msgId: Long, sourceVoice: Element): Result<MsgElement> {
var file = when(sourceVoice.voice.dataCase!!) { var file = when (sourceVoice.voice.dataCase!!) {
VoiceElement.DataCase.FILE_NAME -> { 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) FileUtils.getFileByMd5(fileMd5)
} }
VoiceElement.DataCase.FILE_PATH -> { VoiceElement.DataCase.FILE_PATH -> {
val filePath = sourceVoice.voice.filePath val filePath = sourceVoice.voice.filePath
File(filePath).inputStream().use { File(filePath).inputStream().use {
FileUtils.saveFileToCache(it) FileUtils.saveFileToCache(it)
} }
} }
VoiceElement.DataCase.FILE_BASE64 -> {
FileUtils.saveFileToCache(ByteArrayInputStream( VoiceElement.DataCase.FILE -> {
Base64.decode(sourceVoice.voice.fileBase64, Base64.DEFAULT) FileUtils.saveFileToCache(
)) sourceVoice.voice.file.toByteArray().inputStream()
)
} }
VoiceElement.DataCase.URL -> {
VoiceElement.DataCase.FILE_URL -> {
val tmp = FileUtils.getTmpFile() val tmp = FileUtils.getTmpFile()
if(DownloadUtils.download(sourceVoice.voice.url, tmp)) { if (DownloadUtils.download(sourceVoice.voice.fileUrl, tmp)) {
tmp.inputStream().use { tmp.inputStream().use {
FileUtils.saveFileToCache(it) FileUtils.saveFileToCache(it)
}.also { }.also {
@ -386,9 +386,10 @@ object NtMsgConvertor {
} }
} else { } else {
tmp.delete() 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")) VoiceElement.DataCase.DATA_NOT_SET -> return Result.failure(IllegalArgumentException("VoiceElement data is not set"))
} }
@ -439,7 +440,10 @@ object NtMsgConvertor {
if (EnableOldBDH.get()) { if (EnableOldBDH.get()) {
if (!(Transfer with when (contact.chatType) { if (!(Transfer with when (contact.chatType) {
MsgConstant.KCHATTYPEGROUP -> Troop(contact.peerUid) 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) MsgConstant.KCHATTYPEGUILD -> Troop(contact.peerUid)
else -> return Result.failure(Exception("Not supported chatType(${contact.chatType}) for VoiceMsg")) else -> return Result.failure(Exception("Not supported chatType(${contact.chatType}) for VoiceMsg"))
} trans VoiceResource(file)) } trans VoiceResource(file))
@ -485,27 +489,31 @@ object NtMsgConvertor {
private suspend fun videoConvertor(contact: Contact, msgId: Long, sourceVideo: Element): Result<MsgElement> { private suspend fun videoConvertor(contact: Contact, msgId: Long, sourceVideo: Element): Result<MsgElement> {
val elem = MsgElement() val elem = MsgElement()
val video = VideoElement() val video = com.tencent.qqnt.kernel.nativeinterface.VideoElement()
val file = when(sourceVideo.video.dataCase!!) { val file = when (sourceVideo.video.dataCase!!) {
io.kritor.message.VideoElement.DataCase.FILE_NAME -> { VideoElement.DataCase.FILE -> {
val fileMd5 = sourceVideo.video.fileName.replace(regex = "[{}\\-]".toRegex(), replacement = "").split(".")[0].lowercase() FileUtils.saveFileToCache(
sourceVideo.video.file.toByteArray().inputStream()
)
}
VideoElement.DataCase.FILE_NAME -> {
val fileMd5 = sourceVideo.video.fileName.replace(regex = "[{}\\-]".toRegex(), replacement = "")
.split(".")[0].lowercase()
FileUtils.getFileByMd5(fileMd5) FileUtils.getFileByMd5(fileMd5)
} }
io.kritor.message.VideoElement.DataCase.FILE_PATH -> {
VideoElement.DataCase.FILE_PATH -> {
val filePath = sourceVideo.video.filePath val filePath = sourceVideo.video.filePath
File(filePath).inputStream().use { File(filePath).inputStream().use {
FileUtils.saveFileToCache(it) FileUtils.saveFileToCache(it)
} }
} }
io.kritor.message.VideoElement.DataCase.FILE_BASE64 -> {
FileUtils.saveFileToCache(ByteArrayInputStream( VideoElement.DataCase.FILE_URL -> {
Base64.decode(sourceVideo.video.fileBase64, Base64.DEFAULT)
))
}
io.kritor.message.VideoElement.DataCase.URL -> {
val tmp = FileUtils.getTmpFile() val tmp = FileUtils.getTmpFile()
if(DownloadUtils.download(sourceVideo.video.url, tmp)) { if (DownloadUtils.download(sourceVideo.video.fileUrl, tmp)) {
tmp.inputStream().use { tmp.inputStream().use {
FileUtils.saveFileToCache(it) FileUtils.saveFileToCache(it)
}.also { }.also {
@ -513,10 +521,11 @@ object NtMsgConvertor {
} }
} else { } else {
tmp.delete() 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) video.videoMd5 = QQNTWrapperUtil.CppProxy.genFileMd5Hex(file.absolutePath)
@ -543,7 +552,10 @@ object NtMsgConvertor {
if (EnableOldBDH.get()) { if (EnableOldBDH.get()) {
Transfer with when (contact.chatType) { Transfer with when (contact.chatType) {
MsgConstant.KCHATTYPEGROUP -> Troop(contact.peerUid) 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) MsgConstant.KCHATTYPEGUILD -> Troop(contact.peerUid)
else -> return Result.failure(Exception("Not supported chatType(${contact.chatType}) for VideoMsg")) else -> return Result.failure(Exception("Not supported chatType(${contact.chatType}) for VideoMsg"))
} trans VideoResource(file, File(thumbPath.toString())) } trans VideoResource(file, File(thumbPath.toString()))
@ -567,7 +579,11 @@ object NtMsgConvertor {
return Result.success(elem) return Result.success(elem)
} }
private suspend fun basketballConvertor(contact: Contact, msgId: Long, sourceBasketball: Element): Result<MsgElement> { private suspend fun basketballConvertor(
contact: Contact,
msgId: Long,
sourceBasketball: Element
): Result<MsgElement> {
val elem = MsgElement() val elem = MsgElement()
elem.elementType = MsgConstant.KELEMTYPEFACE elem.elementType = MsgConstant.KELEMTYPEFACE
val face = FaceElement() val face = FaceElement()
@ -697,7 +713,11 @@ object NtMsgConvertor {
} }
private suspend fun locationConvertor(contact: Contact, msgId: Long, sourceLocation: Element): Result<MsgElement> { private suspend fun locationConvertor(contact: Contact, msgId: Long, sourceLocation: Element): Result<MsgElement> {
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) LogCenter.log("无法发送位置分享", Level.ERROR)
} }
return Result.failure(ActionMsgException) return Result.failure(ActionMsgException)
@ -801,7 +821,8 @@ object NtMsgConvertor {
val action = button.action val action = button.action
val permission = action.permission val permission = action.permission
return runCatching { return runCatching {
InlineKeyboardButton(button.id, renderData.label, renderData.visitedLabel, renderData.style, InlineKeyboardButton(
button.id, renderData.label, renderData.visitedLabel, renderData.style,
action.type, 0, action.type, 0,
action.unsupportedTips, action.unsupportedTips,
action.data, false, action.data, false,
@ -811,7 +832,8 @@ object NtMsgConvertor {
false, 0, false, arrayListOf() false, 0, false, arrayListOf()
) )
}.getOrElse { }.getOrElse {
InlineKeyboardButton(button.id, renderData.label, renderData.visitedLabel, renderData.style, InlineKeyboardButton(
button.id, renderData.label, renderData.visitedLabel, renderData.style,
action.type, 0, action.type, 0,
action.unsupportedTips, action.unsupportedTips,
action.data, false, action.data, false,
@ -839,7 +861,7 @@ object NtMsgConvertor {
} }
private suspend fun forwardConvertor(contact: Contact, msgId: Long, sourceForward: Element): Result<MsgElement> { private suspend fun forwardConvertor(contact: Contact, msgId: Long, sourceForward: Element): Result<MsgElement> {
val resId = sourceForward.forward.id val resId = sourceForward.forward.resId
val filename = sourceForward.forward.uniseq val filename = sourceForward.forward.uniseq
var summary = sourceForward.forward.summary var summary = sourceForward.forward.summary
val descriptions = sourceForward.forward.description val descriptions = sourceForward.forward.description

View File

@ -1,10 +1,11 @@
package qq.service.msg package qq.service.msg
import com.tencent.mobileqq.qroute.QRoute import com.tencent.mobileqq.qroute.QRoute
import com.tencent.qqnt.kernel.nativeinterface.*
import com.tencent.qqnt.kernel.nativeinterface.Contact 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 com.tencent.qqnt.msg.api.IMsgService
import io.kritor.message.* import io.kritor.common.*
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.shamrock.helper.ActionMsgException import moe.fuqiuluo.shamrock.helper.ActionMsgException
@ -54,12 +55,12 @@ private object ReqMsgConvertor {
val text = element.textElement val text = element.textElement
val elem = Element.newBuilder() val elem = Element.newBuilder()
if (text.atType != MsgConstant.ATTYPEUNKNOWN) { if (text.atType != MsgConstant.ATTYPEUNKNOWN) {
elem.setAt(atElement { elem.setAt(AtElement.newBuilder().apply {
this.uid = text.atNtUid this.uid = text.atNtUid
this.uin = ContactHelper.getUinByUidAsync(text.atNtUid).toLong() this.uin = ContactHelper.getUinByUidAsync(text.atNtUid).toLong()
}) })
} else { } else {
elem.setText(textElement { elem.setText(TextElement.newBuilder().apply {
this.text = text.content this.text = text.content
}) })
} }
@ -70,28 +71,32 @@ private object ReqMsgConvertor {
val face = element.faceElement val face = element.faceElement
val elem = Element.newBuilder() val elem = Element.newBuilder()
if (face.faceType == 5) { if (face.faceType == 5) {
elem.setPoke(pokeElement { elem.setPoke(PokeElement.newBuilder().apply {
this.id = face.vaspokeId this.id = face.vaspokeId
this.type = face.pokeType this.type = face.pokeType
this.strength = face.pokeStrength this.strength = face.pokeStrength
}) })
} else { } else {
when(face.faceIndex) { when (face.faceIndex) {
114 -> elem.setBasketball(basketballElement { 114 -> elem.setBasketball(BasketballElement.newBuilder().apply {
this.id = face.resultId.ifNullOrEmpty { "0" }?.toInt() ?: 0 this.id = face.resultId.ifNullOrEmpty { "0" }?.toInt() ?: 0
}) })
358 -> elem.setDice(diceElement {
358 -> elem.setDice(DiceElement.newBuilder().apply {
this.id = face.resultId.ifNullOrEmpty { "0" }?.toInt() ?: 0 this.id = face.resultId.ifNullOrEmpty { "0" }?.toInt() ?: 0
}) })
359 -> elem.setRps(rpsElement {
359 -> elem.setRps(RpsElement.newBuilder().apply {
this.id = face.resultId.ifNullOrEmpty { "0" }?.toInt() ?: 0 this.id = face.resultId.ifNullOrEmpty { "0" }?.toInt() ?: 0
}) })
394 -> elem.setFace(faceElement {
394 -> elem.setFace(FaceElement.newBuilder().apply {
this.id = face.faceIndex this.id = face.faceIndex
this.isBig = face.faceType == 3 this.isBig = face.faceType == 3
this.result = face.resultId.ifNullOrEmpty { "1" }?.toInt() ?: 1 this.result = face.resultId.ifNullOrEmpty { "1" }?.toInt() ?: 1
}) })
else -> elem.setFace(faceElement {
else -> elem.setFace(FaceElement.newBuilder().apply {
this.id = face.faceIndex this.id = face.faceIndex
this.isBig = face.faceType == 3 this.isBig = face.faceType == 3
}) })
@ -129,9 +134,9 @@ private object ReqMsgConvertor {
LogCenter.log({ "receive image: $image" }, Level.DEBUG) LogCenter.log({ "receive image: $image" }, Level.DEBUG)
val elem = Element.newBuilder() val elem = Element.newBuilder()
elem.setImage(imageElement { elem.setImage(ImageElement.newBuilder().apply {
this.file = md5 this.fileMd5 = md5
this.url = when (contact.chatType) { this.fileUrl = when (contact.chatType) {
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl( MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(
originalUrl = originalUrl, originalUrl = originalUrl,
md5 = md5, md5 = md5,
@ -164,12 +169,13 @@ private object ReqMsgConvertor {
sha = "", sha = "",
fileSize = image.fileSize.toULong(), fileSize = image.fileSize.toULong(),
peer = contact.longPeer().toString(), peer = contact.longPeer().toString(),
subPeer ="0" subPeer = "0"
) )
else -> throw UnsupportedOperationException("Not supported chat type: ${contact.chatType}") 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 this.type =
if (image.isFlashPic == true) ImageElement.ImageType.FLASH else if (image.original) ImageElement.ImageType.ORIGIN else ImageElement.ImageType.COMMON
this.subType = image.picSubType this.subType = image.picSubType
}) })
@ -184,14 +190,18 @@ private object ReqMsgConvertor {
ptt.fileName.substring(5) ptt.fileName.substring(5)
else ptt.md5HexStr else ptt.md5HexStr
elem.setVoice(voiceElement { elem.setVoice(VoiceElement.newBuilder().apply {
this.url = when (contact.chatType) { this.fileUrl = when (contact.chatType) {
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPttDownUrl("0", ptt.fileUuid) MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPttDownUrl("0", ptt.fileUuid)
MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupPttDownUrl("0", md5.hex2ByteArray(), ptt.fileUuid) MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupPttDownUrl(
"0",
md5.hex2ByteArray(),
ptt.fileUuid
)
else -> throw UnsupportedOperationException("Not supported chat type: ${contact.chatType}") else -> throw UnsupportedOperationException("Not supported chat type: ${contact.chatType}")
} }
this.file = md5 this.fileMd5 = md5
this.magic = ptt.voiceChangeType != MsgConstant.KPTTVOICECHANGETYPENONE this.magic = ptt.voiceChangeType != MsgConstant.KPTTVOICECHANGETYPENONE
}) })
@ -208,9 +218,9 @@ private object ReqMsgConvertor {
it[it.size - 2].hex2ByteArray() it[it.size - 2].hex2ByteArray()
} }
} else video.fileName.split(".")[0].hex2ByteArray() } else video.fileName.split(".")[0].hex2ByteArray()
elem.setVideo(videoElement { elem.setVideo(VideoElement.newBuilder().apply {
this.file = md5.toHexString() this.fileMd5 = md5.toHexString()
this.url = when (contact.chatType) { this.fileUrl = when (contact.chatType) {
MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid) MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid)
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", md5, video.fileUuid) MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", md5, video.fileUuid)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid) MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid)
@ -223,7 +233,11 @@ private object ReqMsgConvertor {
suspend fun convertMarketFace(contact: Contact, element: MsgElement): Result<Element> { suspend fun convertMarketFace(contact: Contact, element: MsgElement): Result<Element> {
val marketFace = element.marketFaceElement val marketFace = element.marketFaceElement
val elem = Element.newBuilder() 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<Element> { suspend fun convertStructJson(contact: Contact, element: MsgElement): Result<Element> {
@ -232,8 +246,8 @@ private object ReqMsgConvertor {
when (data["app"].asString) { when (data["app"].asString) {
"com.tencent.multimsg" -> { "com.tencent.multimsg" -> {
val info = data["meta"].asJsonObject["detail"].asJsonObject val info = data["meta"].asJsonObject["detail"].asJsonObject
elem.setForward(forwardElement { elem.setForward(ForwardElement.newBuilder().apply {
this.id = info["resid"].asString this.resId = info["resid"].asString
this.uniseq = info["uniseq"].asString this.uniseq = info["uniseq"].asString
this.summary = info["summary"].asString this.summary = info["summary"].asString
this.description = info["news"].asJsonArray.joinToString("\n") { this.description = info["news"].asJsonArray.joinToString("\n") {
@ -244,7 +258,7 @@ private object ReqMsgConvertor {
"com.tencent.troopsharecard" -> { "com.tencent.troopsharecard" -> {
val info = data["meta"].asJsonObject["contact"].asJsonObject val info = data["meta"].asJsonObject["contact"].asJsonObject
elem.setContact(contactElement { elem.setContact(ContactElement.newBuilder().apply {
this.scene = Scene.GROUP this.scene = Scene.GROUP
this.peer = info["jumpUrl"].asString.split("group_code=")[1] this.peer = info["jumpUrl"].asString.split("group_code=")[1]
}) })
@ -252,7 +266,7 @@ private object ReqMsgConvertor {
"com.tencent.contact.lua" -> { "com.tencent.contact.lua" -> {
val info = data["meta"].asJsonObject["contact"].asJsonObject val info = data["meta"].asJsonObject["contact"].asJsonObject
elem.setContact(contactElement { elem.setContact(ContactElement.newBuilder().apply {
this.scene = Scene.FRIEND this.scene = Scene.FRIEND
this.peer = info["jumpUrl"].asString.split("uin=")[1] this.peer = info["jumpUrl"].asString.split("uin=")[1]
}) })
@ -260,7 +274,7 @@ private object ReqMsgConvertor {
"com.tencent.map" -> { "com.tencent.map" -> {
val info = data["meta"].asJsonObject["Location.Search"].asJsonObject val info = data["meta"].asJsonObject["Location.Search"].asJsonObject
elem.setLocation(locationElement { elem.setLocation(LocationElement.newBuilder().apply {
this.lat = info["lat"].asString.toFloat() this.lat = info["lat"].asString.toFloat()
this.lon = info["lng"].asString.toFloat() this.lon = info["lng"].asString.toFloat()
this.address = info["address"].asString this.address = info["address"].asString
@ -268,7 +282,7 @@ private object ReqMsgConvertor {
}) })
} }
else -> elem.setJson(jsonElement { else -> elem.setJson(JsonElement.newBuilder().apply {
this.json = data.toString() this.json = data.toString()
}) })
} }
@ -278,20 +292,21 @@ private object ReqMsgConvertor {
suspend fun convertReply(contact: Contact, element: MsgElement): Result<Element> { suspend fun convertReply(contact: Contact, element: MsgElement): Result<Element> {
val reply = element.replyElement val reply = element.replyElement
val elem = Element.newBuilder() val elem = Element.newBuilder()
elem.setReply(replyElement { elem.setReply(ReplyElement.newBuilder().apply {
val msgSeq = reply.replayMsgSeq val msgSeq = reply.replayMsgSeq
val sourceRecords = withTimeoutOrNull(3000) { val sourceRecords = withTimeoutOrNull(3000) {
suspendCancellableCoroutine { suspendCancellableCoroutine {
QRoute.api(IMsgService::class.java).getMsgsBySeqAndCount(contact, msgSeq, 1, true) { _, _, records -> QRoute.api(IMsgService::class.java)
it.resume(records) .getMsgsBySeqAndCount(contact, msgSeq, 1, true) { _, _, records ->
} it.resume(records)
}
} }
} }
if (sourceRecords.isNullOrEmpty()) { if (sourceRecords.isNullOrEmpty()) {
LogCenter.log("无法查询到回复的消息ID: seq = $msgSeq", Level.WARN) LogCenter.log("无法查询到回复的消息ID: seq = $msgSeq", Level.WARN)
this.messageId = reply.replayMsgId this.messageId = reply.replayMsgId.toString()
} else { } else {
this.messageId = sourceRecords.first().msgId this.messageId = sourceRecords.first().msgId.toString()
} }
}) })
return Result.success(elem.build()) return Result.success(elem.build())
@ -307,11 +322,17 @@ private object ReqMsgConvertor {
val fileSubId = fileMsg.fileSubId ?: "" val fileSubId = fileMsg.fileSubId ?: ""
val url = when (contact.chatType) { val url = when (contact.chatType) {
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId) MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildFileDownUrl(contact.guildId, contact.longPeer().toString(), fileId, bizId) MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildFileDownUrl(
contact.guildId,
contact.longPeer().toString(),
fileId,
bizId
)
else -> RichProtoSvc.getGroupFileDownUrl(contact.longPeer(), fileId, bizId) else -> RichProtoSvc.getGroupFileDownUrl(contact.longPeer(), fileId, bizId)
} }
val elem = Element.newBuilder() val elem = Element.newBuilder()
elem.setFile(fileElement { elem.setFile(FileElement.newBuilder().apply {
this.name = fileName this.name = fileName
this.size = fileSize this.size = fileSize
this.url = url this.url = url
@ -326,7 +347,7 @@ private object ReqMsgConvertor {
suspend fun convertMarkdown(contact: Contact, element: MsgElement): Result<Element> { suspend fun convertMarkdown(contact: Contact, element: MsgElement): Result<Element> {
val markdown = element.markdownElement val markdown = element.markdownElement
val elem = Element.newBuilder() val elem = Element.newBuilder()
elem.setMarkdown(markdownElement { elem.setMarkdown(MarkdownElement.newBuilder().apply {
this.markdown = markdown.content this.markdown = markdown.content
}) })
return Result.success(elem.build()) return Result.success(elem.build())
@ -335,7 +356,7 @@ private object ReqMsgConvertor {
suspend fun convertBubbleFace(contact: Contact, element: MsgElement): Result<Element> { suspend fun convertBubbleFace(contact: Contact, element: MsgElement): Result<Element> {
val bubbleFace = element.faceBubbleElement val bubbleFace = element.faceBubbleElement
val elem = Element.newBuilder() val elem = Element.newBuilder()
elem.setBubbleFace(bubbleFaceElement { elem.setBubbleFace(BubbleFaceElement.newBuilder().apply {
this.id = bubbleFace.yellowFaceInfo.index this.id = bubbleFace.yellowFaceInfo.index
this.count = bubbleFace.faceCount ?: 1 this.count = bubbleFace.faceCount ?: 1
}) })
@ -345,38 +366,38 @@ private object ReqMsgConvertor {
suspend fun convertInlineKeyboard(contact: Contact, element: MsgElement): Result<Element> { suspend fun convertInlineKeyboard(contact: Contact, element: MsgElement): Result<Element> {
val inlineKeyboard = element.inlineKeyboardElement val inlineKeyboard = element.inlineKeyboardElement
val elem = Element.newBuilder() val elem = Element.newBuilder()
elem.setButton(buttonElement { elem.setButton(ButtonElement.newBuilder().apply {
inlineKeyboard.rows.forEach { row -> this.addAllRows(inlineKeyboard.rows.map { row ->
this.rows.add(row { ButtonRow.newBuilder().apply {
row.buttons.forEach buttonsLoop@ { button -> this.addAllButtons(row.buttons.map { button ->
if (button == null) return@buttonsLoop Button.newBuilder().apply {
this.buttons.add(button {
this.id = button.id this.id = button.id
this.action = buttonAction { this.renderData = ButtonRender.newBuilder().apply {
this.label = button.label ?: ""
this.visitedLabel = button.visitedLabel ?: ""
this.style = button.style
}.build()
this.action = ButtonAction.newBuilder().apply {
this.type = button.type this.type = button.type
this.permission = buttonActionPermission { this.permission = ButtonActionPermission.newBuilder().apply {
this.type = button.permissionType this.type = button.permissionType
button.specifyRoleIds?.let { button.specifyRoleIds?.let {
this.roleIds.addAll(it) this.addAllRoleIds(it)
} }
button.specifyTinyids?.let { button.specifyTinyids?.let {
this.userIds.addAll(it) this.addAllUserIds(it)
} }
} }.build()
this.unsupportedTips = button.unsupportTips ?: "" this.unsupportedTips = button.unsupportTips ?: ""
this.data = button.data ?: "" this.data = button.data ?: ""
this.reply = button.isReply this.reply = button.isReply
this.enter = button.enter this.enter = button.enter
} }.build()
this.renderData = buttonRender { }.build()
this.label = button.label ?: "" })
this.visitedLabel = button.visitedLabel ?: "" }.build()
this.style = button.style })
} this.botAppid = inlineKeyboard.botAppid
})
}
})
}
}) })
return Result.success(elem.build()) return Result.success(elem.build())
} }

View File

@ -7,11 +7,8 @@ import com.tencent.mobileqq.qroute.QRoute
import com.tencent.qqnt.kernel.nativeinterface.Contact import com.tencent.qqnt.kernel.nativeinterface.Contact
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.msg.api.IMsgService import com.tencent.qqnt.msg.api.IMsgService
import io.kritor.message.AtElement import io.kritor.common.Element
import io.kritor.message.Element import io.kritor.common.ImageElement
import io.kritor.message.ElementType
import io.kritor.message.ImageElement
import io.kritor.message.ImageType
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.Level
@ -68,7 +65,7 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
forEach { forEach {
try { try {
when(it.type!!) { when(it.type!!) {
ElementType.TEXT -> { Element.ElementType.TEXT -> {
val text = it.text.text val text = it.text.text
val elem = Elem( val elem = Elem(
text = TextMsg(text) text = TextMsg(text)
@ -76,13 +73,11 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
elems.add(elem) elems.add(elem)
summary.append(text) summary.append(text)
} }
ElementType.AT -> { Element.ElementType.AT -> {
when (contact.chatType) { when (contact.chatType) {
MsgConstant.KCHATTYPEGROUP -> { MsgConstant.KCHATTYPEGROUP -> {
val qq = when (it.at.accountCase) { val qq = ContactHelper.getUinByUidAsync(it.at.uid)
AtElement.AccountCase.UIN -> it.at.uin.toString()
else -> ContactHelper.getUinByUidAsync(it.at.uid)
}
val type: Int val type: Int
val nick = if (it.at.uid == "all" || it.at.uin == 0L) { val nick = if (it.at.uid == "all" || it.at.uin == 0L) {
type = 1 type = 1
@ -112,10 +107,7 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
} }
MsgConstant.KCHATTYPEC2C -> { MsgConstant.KCHATTYPEC2C -> {
val qq = when (it.at.accountCase) { val qq = ContactHelper.getUinByUidAsync(it.at.uid)
AtElement.AccountCase.UIN -> it.at.uin.toString()
else -> ContactHelper.getUinByUidAsync(it.at.uid)
}
val display = "@" + (ContactHelper.getProfileCard(qq.toLong()).onSuccess { val display = "@" + (ContactHelper.getProfileCard(qq.toLong()).onSuccess {
it.strNick.ifNullOrEmpty { qq } it.strNick.ifNullOrEmpty { qq }
}.onFailure { }.onFailure {
@ -130,7 +122,7 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
else -> throw UnsupportedOperationException("Unsupported chatType($contact) for AtMsg") else -> throw UnsupportedOperationException("Unsupported chatType($contact) for AtMsg")
} }
} }
ElementType.FACE -> { Element.ElementType.FACE -> {
val faceId = it.face.id val faceId = it.face.id
val elem = if (it.face.isBig) { val elem = if (it.face.isBig) {
Elem( Elem(
@ -159,12 +151,12 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
elems.add(elem) elems.add(elem)
summary.append("[表情]") summary.append("[表情]")
} }
ElementType.BUBBLE_FACE -> throw UnsupportedOperationException("Unsupported ElementType.BUBBLE_FACE") Element.ElementType.BUBBLE_FACE -> throw UnsupportedOperationException("Unsupported Element.ElementType.BUBBLE_FACE")
ElementType.REPLY -> { Element.ElementType.REPLY -> {
val msgId = it.reply.messageId val msgId = it.reply.messageId
withTimeoutOrNull(3000) { withTimeoutOrNull(3000) {
suspendCancellableCoroutine { 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) it.resume(records)
} }
} }
@ -191,9 +183,9 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
} }
summary.append("[回复消息]") summary.append("[回复消息]")
} }
ElementType.IMAGE -> { Element.ElementType.IMAGE -> {
val type = it.image.type val type = it.image.type
val isOriginal = type == ImageType.ORIGIN val isOriginal = type == ImageElement.ImageType.ORIGIN
val file = when(it.image.dataCase!!) { val file = when(it.image.dataCase!!) {
ImageElement.DataCase.FILE_NAME -> { ImageElement.DataCase.FILE_NAME -> {
val fileMd5 = it.image.fileName.replace(regex = "[{}\\-]".toRegex(), replacement = "").split(".")[0].lowercase() val fileMd5 = it.image.fileName.replace(regex = "[{}\\-]".toRegex(), replacement = "").split(".")[0].lowercase()
@ -205,16 +197,16 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
FileUtils.saveFileToCache(it) FileUtils.saveFileToCache(it)
} }
} }
ImageElement.DataCase.FILE_BASE64 -> { ImageElement.DataCase.FILE -> {
FileUtils.saveFileToCache( FileUtils.saveFileToCache(
ByteArrayInputStream( ByteArrayInputStream(
Base64.decode(it.image.fileBase64, Base64.DEFAULT) it.image.file.toByteArray()
) )
) )
} }
ImageElement.DataCase.URL -> { ImageElement.DataCase.FILE_URL -> {
val tmp = FileUtils.getTmpFile() val tmp = FileUtils.getTmpFile()
if(DownloadUtils.download(it.image.url, tmp)) { if(DownloadUtils.download(it.image.fileUrl, tmp)) {
tmp.inputStream().use { tmp.inputStream().use {
FileUtils.saveFileToCache(it) FileUtils.saveFileToCache(it)
}.also { }.also {
@ -222,7 +214,7 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
} }
} else { } else {
tmp.delete() tmp.delete()
throw LogicException("图片资源下载失败: ${it.image.url}") throw LogicException("图片资源下载失败: ${it.image.fileUrl}")
} }
} }
ImageElement.DataCase.DATA_NOT_SET -> throw IllegalArgumentException("ImageElement data is not set") ImageElement.DataCase.DATA_NOT_SET -> throw IllegalArgumentException("ImageElement data is not set")
@ -352,10 +344,10 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
summary.append("[图片]") summary.append("[图片]")
} }
ElementType.VOICE -> throw UnsupportedOperationException("Unsupported ElementType.VOICE") Element.ElementType.VOICE -> throw UnsupportedOperationException("Unsupported Element.ElementType.VOICE")
ElementType.VIDEO -> throw UnsupportedOperationException("Unsupported ElementType.VIDEO") Element.ElementType.VIDEO -> throw UnsupportedOperationException("Unsupported Element.ElementType.VIDEO")
ElementType.BASKETBALL -> throw UnsupportedOperationException("Unsupported ElementType.BASKETBALL") Element.ElementType.BASKETBALL -> throw UnsupportedOperationException("Unsupported Element.ElementType.BASKETBALL")
ElementType.DICE -> { Element.ElementType.DICE -> {
val elem = Elem( val elem = Elem(
commonElem = CommonElem( commonElem = CommonElem(
serviceType = 37, serviceType = 37,
@ -375,7 +367,7 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
elems.add(elem) elems.add(elem)
summary .append( "[骰子]" ) summary .append( "[骰子]" )
} }
ElementType.RPS -> { Element.ElementType.RPS -> {
val elem = Elem( val elem = Elem(
commonElem = CommonElem( commonElem = CommonElem(
serviceType = 37, serviceType = 37,
@ -395,7 +387,7 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
elems.add(elem) elems.add(elem)
summary .append( "[包剪锤]" ) summary .append( "[包剪锤]" )
} }
ElementType.POKE -> { Element.ElementType.POKE -> {
val elem = Elem( val elem = Elem(
commonElem = CommonElem( commonElem = CommonElem(
serviceType = 2, serviceType = 2,
@ -410,8 +402,8 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
elems.add(elem) elems.add(elem)
summary .append( "[戳一戳]" ) summary .append( "[戳一戳]" )
} }
ElementType.MUSIC -> throw UnsupportedOperationException("Unsupported ElementType.MUSIC") Element.ElementType.MUSIC -> throw UnsupportedOperationException("Unsupported Element.ElementType.MUSIC")
ElementType.WEATHER -> { Element.ElementType.WEATHER -> {
var code = it.weather.code.toIntOrNull() var code = it.weather.code.toIntOrNull()
if (code == null) { if (code == null) {
val city = it.weather.city val city = it.weather.city
@ -438,12 +430,12 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
throw LogicException("无法获取城市天气") throw LogicException("无法获取城市天气")
} }
} }
ElementType.LOCATION -> throw UnsupportedOperationException("Unsupported ElementType.LOCATION") Element.ElementType.LOCATION -> throw UnsupportedOperationException("Unsupported Element.ElementType.LOCATION")
ElementType.SHARE -> throw UnsupportedOperationException("Unsupported ElementType.SHARE") Element.ElementType.SHARE -> throw UnsupportedOperationException("Unsupported Element.ElementType.SHARE")
ElementType.GIFT -> throw UnsupportedOperationException("Unsupported ElementType.GIFT") Element.ElementType.GIFT -> throw UnsupportedOperationException("Unsupported Element.ElementType.GIFT")
ElementType.MARKET_FACE -> throw UnsupportedOperationException("Unsupported ElementType.MARKET_FACE") Element.ElementType.MARKET_FACE -> throw UnsupportedOperationException("Unsupported Element.ElementType.MARKET_FACE")
ElementType.FORWARD -> { Element.ElementType.FORWARD -> {
val resId = it.forward.id val resId = it.forward.resId
val filename = UUID.randomUUID().toString().uppercase() val filename = UUID.randomUUID().toString().uppercase()
var content = it.forward.summary var content = it.forward.summary
val descriptions = it.forward.description val descriptions = it.forward.description
@ -496,8 +488,8 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
elems.add(elem) elems.add(elem)
summary.append( "[聊天记录]" ) summary.append( "[聊天记录]" )
} }
ElementType.CONTACT -> throw UnsupportedOperationException("Unsupported ElementType.CONTACT") Element.ElementType.CONTACT -> throw UnsupportedOperationException("Unsupported Element.ElementType.CONTACT")
ElementType.JSON -> { Element.ElementType.JSON -> {
val elem = Elem( val elem = Elem(
lightApp = LightAppElem( lightApp = LightAppElem(
data = byteArrayOf(1) + DeflateTools.compress(it.json.json.toByteArray()) data = byteArrayOf(1) + DeflateTools.compress(it.json.json.toByteArray())
@ -506,9 +498,9 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
elems.add(elem) elems.add(elem)
summary .append( "[Json消息]" ) summary .append( "[Json消息]" )
} }
ElementType.XML -> throw UnsupportedOperationException("Unsupported ElementType.XML") Element.ElementType.XML -> throw UnsupportedOperationException("Unsupported Element.ElementType.XML")
ElementType.FILE -> throw UnsupportedOperationException("Unsupported ElementType.FILE") Element.ElementType.FILE -> throw UnsupportedOperationException("Unsupported Element.ElementType.FILE")
ElementType.MARKDOWN -> { Element.ElementType.MARKDOWN -> {
val elem = Elem( val elem = Elem(
commonElem = CommonElem( commonElem = CommonElem(
serviceType = 45, serviceType = 45,
@ -519,7 +511,7 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
elems.add(elem) elems.add(elem)
summary.append("[Markdown消息]") summary.append("[Markdown消息]")
} }
ElementType.BUTTON -> { Element.ElementType.BUTTON -> {
val elem = Elem( val elem = Elem(
commonElem = CommonElem( commonElem = CommonElem(
serviceType = 46, serviceType = 46,
@ -552,7 +544,7 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
) )
}) })
}, },
appid = 0 appid = it.button.botAppid.toULong()
) )
).toByteArray(), ).toByteArray(),
businessType = 1 businessType = 1
@ -561,8 +553,7 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
elems.add(elem) elems.add(elem)
summary.append("[Button消息]") summary.append("[Button消息]")
} }
ElementType.NODE -> throw UnsupportedOperationException("Unsupported ElementType.NODE") Element.ElementType.UNRECOGNIZED -> throw UnsupportedOperationException("Unsupported Element.ElementType.UNRECOGNIZED")
ElementType.UNRECOGNIZED -> throw UnsupportedOperationException("Unsupported ElementType.UNRECOGNIZED")
} }
} catch (e: Throwable) { } catch (e: Throwable) {
LogCenter.log("转换消息失败(Multi): ${e.stackTraceToString()}", Level.ERROR) LogCenter.log("转换消息失败(Multi): ${e.stackTraceToString()}", Level.ERROR)