16 Commits

Author SHA1 Message Date
59d762eecf fix https://github.com/KarinJS/kritor/issues/10 2024-04-11 12:37:54 +08:00
e891bc8512 fix build 2024-04-11 01:17:13 +08:00
494c70f2f8 update kritor 2024-04-11 00:58:36 +08:00
7baf459b2a Shamrock: fix scene and group code 2024-04-10 21:09:10 +08:00
36a09ca088 update kritor 2024-04-08 20:00:44 +08:00
926c4659f6 Shamrock: add kritor metadata 2024-04-07 16:51:21 +08:00
cb7c68f36c fix: build error 2024-04-07 16:27:35 +08:00
72af39208c update kritor 2024-04-07 16:08:33 +08:00
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
29 changed files with 1118 additions and 922 deletions

View File

@ -37,17 +37,18 @@ android {
dependencies {
protobuf(files("kritor/protos"))
implementation("com.google.protobuf:protobuf-java:4.26.0")
implementation(kotlinx("coroutines-core", "1.8.0"))
implementation("com.google.protobuf:protobuf-java:3.25.3")
implementation(grpc("stub", "1.62.2"))
implementation(grpc("kotlin-stub", "1.4.1"))
implementation(grpc("protobuf", "1.62.2"))
implementation(grpc("kotlin-stub", "1.4.1"))
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.25.3"
artifact = "com.google.protobuf:protoc:4.26.0"
}
plugins {
create("grpc") {

View File

@ -5,22 +5,9 @@ package moe.fuqiuluo.ksp.impl
import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.getAnnotationsByType
import com.google.devtools.ksp.getClassDeclarationByName
import com.google.devtools.ksp.getJavaClassByName
import com.google.devtools.ksp.getKotlinClassByName
import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.Dependencies
import com.google.devtools.ksp.processing.KSPLogger
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.*
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSDeclaration
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
import com.google.devtools.ksp.symbol.KSType
import com.google.devtools.ksp.symbol.KSTypeParameter
import com.google.devtools.ksp.symbol.Modifier
import com.google.devtools.ksp.validate
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
@ -67,15 +54,16 @@ class GrpcProcessor(
}
funcBuilder.addStatement("return EMPTY_BYTE_ARRAY")
fileSpec
.addStatement("import io.kritor.*")
.addStatement("import io.kritor.authentication.*")
.addStatement("import io.kritor.core.*")
.addStatement("import io.kritor.contact.*")
.addStatement("import io.kritor.group.*")
.addStatement("import io.kritor.friend.*")
.addStatement("import io.kritor.customization.*")
.addStatement("import io.kritor.developer.*")
.addStatement("import io.kritor.file.*")
.addStatement("import io.kritor.friend.*")
.addStatement("import io.kritor.group.*")
.addStatement("import io.kritor.guild.*")
.addStatement("import io.kritor.message.*")
.addStatement("import io.kritor.web.*")
.addStatement("import io.kritor.developer.*")
.addFunction(funcBuilder.build())
.addImport("moe.fuqiuluo.symbols", "EMPTY_BYTE_ARRAY")
runCatching {

View File

@ -2,13 +2,21 @@
package kritor.client
import com.google.protobuf.ByteString
import io.grpc.CallOptions
import io.grpc.Channel
import io.grpc.ClientCall
import io.grpc.ClientInterceptor
import io.grpc.ForwardingClientCall
import io.grpc.Metadata
import io.grpc.ManagedChannel
import io.grpc.ManagedChannelBuilder
import io.kritor.ReverseServiceGrpcKt
import io.kritor.event.*
import io.kritor.reverse.ReqCode
import io.kritor.reverse.Request
import io.kritor.reverse.Response
import io.grpc.MethodDescriptor
import io.kritor.common.Request
import io.kritor.common.Response
import io.kritor.event.EventServiceGrpcKt
import io.kritor.event.EventStructure
import io.kritor.event.EventType
import io.kritor.reverse.ReverseServiceGrpcKt
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
@ -22,6 +30,8 @@ import kritor.handlers.handleGrpc
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.internals.GlobalEventTransmitter
import moe.fuqiuluo.shamrock.tools.ShamrockVersion
import qq.service.ticket.TicketHelper
import kotlin.time.Duration.Companion.seconds
internal class KritorClient(
@ -38,11 +48,26 @@ internal class KritorClient(
if (::channel.isInitialized && isActive()){
channel.shutdown()
}
val interceptor = object : ClientInterceptor {
override fun <ReqT, RespT> interceptCall(method: MethodDescriptor<ReqT, RespT>, callOptions: CallOptions, next: Channel): ClientCall<ReqT, RespT> {
return object : ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {
override fun start(responseListener: Listener<RespT>, headers: Metadata) {
headers.merge(Metadata().apply {
put(Metadata.Key.of("kritor-self-uin", Metadata.ASCII_STRING_MARSHALLER), TicketHelper.getUin())
put(Metadata.Key.of("kritor-self-uid", Metadata.ASCII_STRING_MARSHALLER), TicketHelper.getUid())
put(Metadata.Key.of("kritor-self-version", Metadata.ASCII_STRING_MARSHALLER), "OpenShamrock-$ShamrockVersion")
})
super.start(responseListener, headers)
}
}
}
}
channel = ManagedChannelBuilder
.forAddress(host, port)
.usePlaintext()
.enableRetry() // 允许尝试
.executor(Dispatchers.IO.asExecutor()) // 使用协程的调度器
.intercept(interceptor)
.build()
}.onFailure {
LogCenter.log("KritorClient start failed: ${it.stackTraceToString()}", Level.ERROR)
@ -113,7 +138,7 @@ internal class KritorClient(
val rsp = handleGrpc(request.cmd, request.buf.toByteArray())
senderChannel.emit(Response.newBuilder()
.setCmd(request.cmd)
.setCode(ReqCode.SUCCESS)
.setCode(Response.ResponseCode.SUCCESS)
.setMsg("success")
.setSeq(request.seq)
.setBuf(ByteString.copyFrom(rsp))
@ -121,7 +146,7 @@ internal class KritorClient(
}.onFailure {
senderChannel.emit(Response.newBuilder()
.setCmd(request.cmd)
.setCode(ReqCode.INTERNAL)
.setCode(Response.ResponseCode.INTERNAL)
.setMsg(it.stackTraceToString())
.setSeq(request.seq)
.setBuf(ByteString.EMPTY)

View File

@ -2,7 +2,12 @@
package kritor.server
import io.grpc.Grpc
import io.grpc.Metadata
import io.grpc.InsecureServerCredentials
import io.grpc.ServerCall
import io.grpc.ServerCallHandler
import io.grpc.ServerInterceptor
import io.grpc.ForwardingServerCall.SimpleForwardingServerCall;
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -10,25 +15,45 @@ import kotlinx.coroutines.asExecutor
import kritor.auth.AuthInterceptor
import kritor.service.*
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.tools.ShamrockVersion
import qq.service.ticket.TicketHelper
import kotlin.coroutines.CoroutineContext
class KritorServer(
private val port: Int
): CoroutineScope {
private val serverInterceptor = object : ServerInterceptor {
override fun <ReqT, RespT> interceptCall(
call: ServerCall<ReqT, RespT>, headers: Metadata, next: ServerCallHandler<ReqT, RespT>
): ServerCall.Listener<ReqT> {
return next.startCall(object : SimpleForwardingServerCall<ReqT, RespT>(call) {
override fun sendHeaders(headers: Metadata?) {
headers?.apply {
put(Metadata.Key.of("kritor-self-uin", Metadata.ASCII_STRING_MARSHALLER), TicketHelper.getUin())
put(Metadata.Key.of("kritor-self-uid", Metadata.ASCII_STRING_MARSHALLER), TicketHelper.getUid())
put(Metadata.Key.of("kritor-self-version", Metadata.ASCII_STRING_MARSHALLER), "OpenShamrock-$ShamrockVersion")
}
super.sendHeaders(headers)
}
}, headers)
}
}
private val server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create())
.executor(Dispatchers.IO.asExecutor())
.intercept(AuthInterceptor)
.addService(Authentication)
.addService(ContactService)
.addService(KritorService)
.intercept(serverInterceptor)
.addService(AuthenticationService)
.addService(CoreService)
.addService(FriendService)
.addService(GroupService)
.addService(GroupFileService)
.addService(MessageService)
.addService(EventService)
.addService(ForwardMessageService)
.addService(WebService)
.addService(DeveloperService)
.addService(QsignService)
.build()!!
fun start(block: Boolean = false) {

View File

@ -2,23 +2,19 @@ package kritor.service
import io.grpc.Status
import io.grpc.StatusRuntimeException
import io.kritor.AuthCode
import io.kritor.AuthReq
import io.kritor.AuthRsp
import io.kritor.AuthenticationGrpcKt
import io.kritor.GetAuthStateReq
import io.kritor.GetAuthStateRsp
import io.kritor.authentication.*
import io.kritor.authentication.AuthenticateResponse.AuthenticateResponseCode
import kritor.auth.AuthInterceptor
import moe.fuqiuluo.shamrock.config.ActiveTicket
import moe.fuqiuluo.shamrock.config.ShamrockConfig
import qq.service.QQInterfaces
internal object Authentication: AuthenticationGrpcKt.AuthenticationCoroutineImplBase() {
@Grpc("Authentication", "Auth")
override suspend fun auth(request: AuthReq): AuthRsp {
internal object AuthenticationService: AuthenticationServiceGrpcKt.AuthenticationServiceCoroutineImplBase() {
@Grpc("AuthenticationService", "Authenticate")
override suspend fun authenticate(request: AuthenticateRequest): AuthenticateResponse {
if (QQInterfaces.app.account != request.account) {
return AuthRsp.newBuilder().apply {
code = AuthCode.NO_ACCOUNT
return AuthenticateResponse.newBuilder().apply {
code = AuthenticateResponseCode.NO_ACCOUNT
msg = "No such account"
}.build()
}
@ -29,36 +25,36 @@ internal object Authentication: AuthenticationGrpcKt.AuthenticationCoroutineImpl
val ticket = ShamrockConfig.getProperty(activeTicketName + if (index == 0) "" else ".$index", null)
if (ticket.isNullOrEmpty()) {
if (index == 0) {
return AuthRsp.newBuilder().apply {
code = AuthCode.OK
return AuthenticateResponse.newBuilder().apply {
code = AuthenticateResponseCode.OK
msg = "OK"
}.build()
} else {
break
}
} else if (ticket == request.ticket) {
return AuthRsp.newBuilder().apply {
code = AuthCode.OK
return AuthenticateResponse.newBuilder().apply {
code = AuthenticateResponseCode.OK
msg = "OK"
}.build()
}
index++
}
return AuthRsp.newBuilder().apply {
code = AuthCode.NO_TICKET
return AuthenticateResponse.newBuilder().apply {
code = AuthenticateResponseCode.NO_TICKET
msg = "Invalid ticket"
}.build()
}
@Grpc("Authentication", "GetAuthState")
override suspend fun getAuthState(request: GetAuthStateReq): GetAuthStateRsp {
@Grpc("AuthenticationService", "GetAuthenticationState")
override suspend fun getAuthenticationState(request: GetAuthenticationStateRequest): GetAuthenticationStateResponse {
if (request.account != QQInterfaces.app.account) {
throw StatusRuntimeException(Status.CANCELLED.withDescription("No such account"))
}
return GetAuthStateRsp.newBuilder().apply {
isRequiredAuth = AuthInterceptor.getAllTicket().isNotEmpty()
return GetAuthenticationStateResponse.newBuilder().apply {
isRequired = AuthInterceptor.getAllTicket().isNotEmpty()
}.build()
}
}

View File

@ -1,187 +0,0 @@
package kritor.service
import android.os.Bundle
import com.tencent.mobileqq.profilecard.api.IProfileCardBlacklistApi
import com.tencent.mobileqq.profilecard.api.IProfileProtocolConst.*
import com.tencent.mobileqq.profilecard.api.IProfileProtocolService
import com.tencent.mobileqq.qroute.QRoute
import io.grpc.Status
import io.grpc.StatusRuntimeException
import io.kritor.contact.ContactServiceGrpcKt
import io.kritor.contact.GetUidRequest
import io.kritor.contact.GetUidResponse
import io.kritor.contact.GetUinByUidRequest
import io.kritor.contact.GetUinByUidResponse
import io.kritor.contact.IsBlackListUserRequest
import io.kritor.contact.IsBlackListUserResponse
import io.kritor.contact.ProfileCard
import io.kritor.contact.ProfileCardRequest
import io.kritor.contact.SetProfileCardRequest
import io.kritor.contact.SetProfileCardResponse
import io.kritor.contact.StrangerExt
import io.kritor.contact.StrangerInfo
import io.kritor.contact.StrangerInfoRequest
import io.kritor.contact.VoteUserRequest
import io.kritor.contact.VoteUserResponse
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull
import qq.service.QQInterfaces
import qq.service.contact.ContactHelper
import kotlin.coroutines.resume
internal object ContactService : ContactServiceGrpcKt.ContactServiceCoroutineImplBase() {
@Grpc("ContactService", "VoteUser")
override suspend fun voteUser(request: VoteUserRequest): VoteUserResponse {
ContactHelper.voteUser(
when (request.accountCase!!) {
VoteUserRequest.AccountCase.ACCOUNT_UIN -> request.accountUin
VoteUserRequest.AccountCase.ACCOUNT_UID -> ContactHelper.getUinByUidAsync(request.accountUid).toLong()
VoteUserRequest.AccountCase.ACCOUNT_NOT_SET -> throw StatusRuntimeException(
Status.INVALID_ARGUMENT
.withDescription("account not set")
)
}, request.voteCount
).onFailure {
throw StatusRuntimeException(
Status.INTERNAL
.withDescription(it.stackTraceToString())
)
}
return VoteUserResponse.newBuilder().build()
}
@Grpc("ContactService", "GetProfileCard")
override suspend fun getProfileCard(request: ProfileCardRequest): ProfileCard {
val uin = when (request.accountCase!!) {
ProfileCardRequest.AccountCase.ACCOUNT_UIN -> request.accountUin
ProfileCardRequest.AccountCase.ACCOUNT_UID -> ContactHelper.getUinByUidAsync(request.accountUid).toLong()
ProfileCardRequest.AccountCase.ACCOUNT_NOT_SET -> throw StatusRuntimeException(
Status.INVALID_ARGUMENT
.withDescription("account not set")
)
}
val contact = ContactHelper.getProfileCard(uin)
contact.onFailure {
throw StatusRuntimeException(
Status.INTERNAL
.withDescription(it.stackTraceToString())
)
}
contact.onSuccess {
return ProfileCard.newBuilder().apply {
this.uin = it.uin.toLong()
this.uid = if (request.hasAccountUid()) request.accountUid
else ContactHelper.getUidByUinAsync(it.uin.toLong())
this.name = it.strNick ?: ""
this.remark = it.strReMark ?: ""
this.level = it.iQQLevel
this.birthday = it.lBirthday
this.loginDay = it.lLoginDays.toInt()
this.voteCnt = it.lVoteCount.toInt()
this.qid = it.qid ?: ""
this.isSchoolVerified = it.schoolVerifiedFlag
}.build()
}
throw StatusRuntimeException(
Status.INTERNAL
.withDescription("logic failed")
)
}
@Grpc("ContactService", "GetStrangerInfo")
override suspend fun getStrangerInfo(request: StrangerInfoRequest): StrangerInfo {
val userId = request.uin
val info = ContactHelper.refreshAndGetProfileCard(userId).onFailure {
throw StatusRuntimeException(
Status.INTERNAL
.withCause(it)
.withDescription("Unable to fetch stranger info")
)
}.getOrThrow()
return StrangerInfo.newBuilder().apply {
this.uid = ContactHelper.getUidByUinAsync(userId)
this.uin = (info.uin ?: "0").toLong()
this.name = info.strNick ?: ""
this.level = info.iQQLevel
this.loginDay = info.lLoginDays.toInt()
this.voteCnt = info.lVoteCount.toInt()
this.qid = info.qid ?: ""
this.isSchoolVerified = info.schoolVerifiedFlag
this.ext = StrangerExt.newBuilder().apply {
this.bigVip = info.bBigClubVipOpen == 1.toByte()
this.hollywoodVip = info.bHollywoodVipOpen == 1.toByte()
this.qqVip = info.bQQVipOpen == 1.toByte()
this.superVip = info.bSuperQQOpen == 1.toByte()
this.voted = info.bVoted == 1.toByte()
}.build().toByteString()
}.build()
}
@Grpc("ContactService", "GetUid")
override suspend fun getUid(request: GetUidRequest): GetUidResponse {
return GetUidResponse.newBuilder().apply {
request.uinList.forEach {
putUid(it, ContactHelper.getUidByUinAsync(it))
}
}.build()
}
@Grpc("ContactService", "GetUinByUid")
override suspend fun getUinByUid(request: GetUinByUidRequest): GetUinByUidResponse {
return GetUinByUidResponse.newBuilder().apply {
request.uidList.forEach {
putUin(it, ContactHelper.getUinByUidAsync(it).toLong())
}
}.build()
}
@Grpc("ContactService", "SetProfileCard")
override suspend fun setProfileCard(request: SetProfileCardRequest): SetProfileCardResponse {
val bundle = Bundle()
val service = QQInterfaces.app
.getRuntimeService(IProfileProtocolService::class.java, "all")
if (request.hasNickName()) {
bundle.putString(KEY_NICK, request.nickName)
}
if (request.hasCompany()) {
bundle.putString(KEY_COMPANY, request.company)
}
if (request.hasEmail()) {
bundle.putString(KEY_EMAIL, request.email)
}
if (request.hasCollege()) {
bundle.putString(KEY_COLLEGE, request.college)
}
if (request.hasPersonalNote()) {
bundle.putString(KEY_PERSONAL_NOTE, request.personalNote)
}
if (request.hasBirthday()) {
bundle.putInt(KEY_BIRTHDAY, request.birthday)
}
if (request.hasAge()) {
bundle.putInt(KEY_AGE, request.age)
}
service.setProfileDetail(bundle)
return super.setProfileCard(request)
}
@Grpc("ContactService", "IsBlackListUser")
override suspend fun isBlackListUser(request: IsBlackListUserRequest): IsBlackListUserResponse {
val blacklistApi = QRoute.api(IProfileCardBlacklistApi::class.java)
val isBlack = withTimeoutOrNull(5000) {
suspendCancellableCoroutine { continuation ->
blacklistApi.isBlackOrBlackedUin(request.uin.toString()) {
continuation.resume(it)
}
}
} ?: false
return IsBlackListUserResponse.newBuilder().setIsBlackListUser(isBlack).build()
}
}

View File

@ -9,15 +9,13 @@ import moe.fuqiuluo.shamrock.tools.ShamrockVersion
import moe.fuqiuluo.shamrock.utils.DownloadUtils
import moe.fuqiuluo.shamrock.utils.FileUtils
import moe.fuqiuluo.shamrock.utils.MD5
import moe.fuqiuluo.shamrock.utils.MMKVFetcher
import moe.fuqiuluo.shamrock.utils.PlatformUtils
import mqq.app.MobileQQ
import qq.service.QQInterfaces.Companion.app
import qq.service.contact.ContactHelper
import java.io.File
internal object KritorService : KritorServiceGrpcKt.KritorServiceCoroutineImplBase() {
@Grpc("KritorService", "GetVersion")
internal object CoreService : CoreServiceGrpcKt.CoreServiceCoroutineImplBase() {
@Grpc("CoreService", "GetVersion")
override suspend fun getVersion(request: GetVersionRequest): GetVersionResponse {
return GetVersionResponse.newBuilder().apply {
this.version = ShamrockVersion
@ -25,15 +23,7 @@ internal object KritorService : KritorServiceGrpcKt.KritorServiceCoroutineImplBa
}.build()
}
@Grpc("KritorService", "ClearCache")
override suspend fun clearCache(request: ClearCacheRequest): ClearCacheResponse {
FileUtils.clearCache()
MMKVFetcher.mmkvWithId("audio2silk")
.clear()
return ClearCacheResponse.newBuilder().build()
}
@Grpc("KritorService", "GetCurrentAccount")
@Grpc("CoreService", "GetCurrentAccount")
override suspend fun getCurrentAccount(request: GetCurrentAccountRequest): GetCurrentAccountResponse {
return GetCurrentAccountResponse.newBuilder().apply {
this.accountName = if (app is QQAppInterface) app.currentNickname else "unknown"
@ -42,7 +32,7 @@ internal object KritorService : KritorServiceGrpcKt.KritorServiceCoroutineImplBa
}.build()
}
@Grpc("KritorService", "DownloadFile")
@Grpc("CoreService", "DownloadFile")
override suspend fun downloadFile(request: DownloadFileRequest): DownloadFileResponse {
val headerMap = mutableMapOf(
"User-Agent" to "Shamrock"
@ -88,7 +78,7 @@ internal object KritorService : KritorServiceGrpcKt.KritorServiceCoroutineImplBa
}.build()
}
@Grpc("KritorService", "SwitchAccount")
@Grpc("CoreService", "SwitchAccount")
override suspend fun switchAccount(request: SwitchAccountRequest): SwitchAccountResponse {
val uin = when (request.accountCase!!) {
SwitchAccountRequest.AccountCase.ACCOUNT_UID -> ContactHelper.getUinByUidAsync(request.accountUid)
@ -108,15 +98,4 @@ internal object KritorService : KritorServiceGrpcKt.KritorServiceCoroutineImplBa
}
return SwitchAccountResponse.newBuilder().build()
}
@Grpc("KritorService", "GetDeviceBattery")
override suspend fun getDeviceBattery(request: GetDeviceBatteryRequest): GetDeviceBatteryResponse {
return GetDeviceBatteryResponse.newBuilder().apply {
PlatformUtils.getDeviceBattery().let {
this.battery = it.battery
this.scale = it.scale
this.status = it.status
}
}.build()
}
}

View File

@ -1,29 +1,56 @@
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.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.shamrock.utils.FileUtils
import moe.fuqiuluo.shamrock.utils.MMKVFetcher
import moe.fuqiuluo.shamrock.utils.PlatformUtils
import qq.service.QQInterfaces
import java.io.File
internal object DeveloperService: DeveloperServiceGrpcKt.DeveloperServiceCoroutineImplBase() {
@Grpc("DeveloperService", "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.sign = ByteString.copyFrom(result.sign)
this.token = ByteString.copyFrom(result.token)
this.extra = ByteString.copyFrom(result.extra)
@Grpc("DeveloperService", "Shell")
override suspend fun shell(request: ShellRequest): ShellResponse {
if (request.commandList.isEmpty()) return ShellResponse.newBuilder().setIsSuccess(false).build()
val runtime = Runtime.getRuntime()
val result = withTimeoutOrNull(5000L) {
withContext(Dispatchers.IO) {
runtime.exec(request.commandList.toTypedArray(), null, File(request.directory)).apply { waitFor() }
}
}
return ShellResponse.newBuilder().apply {
if (result == null) {
isSuccess = false
} else {
isSuccess = true
result.inputStream.use {
data = it.readBytes().toString(Charsets.UTF_8)
}
}
}.build()
}
@Grpc("DeveloperService", "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()
@Grpc("DeveloperService", "ClearCache")
override suspend fun clearCache(request: ClearCacheRequest): ClearCacheResponse {
FileUtils.clearCache()
MMKVFetcher.mmkvWithId("audio2silk")
.clear()
return ClearCacheResponse.newBuilder().build()
}
@Grpc("DeveloperService", "GetDeviceBattery")
override suspend fun getDeviceBattery(request: GetDeviceBatteryRequest): GetDeviceBatteryResponse {
return GetDeviceBatteryResponse.newBuilder().apply {
PlatformUtils.getDeviceBattery().let {
this.battery = it.battery
this.scale = it.scale
this.status = it.status
}
}.build()
}
@Grpc("DeveloperService", "SendPacket")
override suspend fun sendPacket(request: SendPacketRequest): SendPacketResponse {
@ -37,5 +64,4 @@ internal object DeveloperService: DeveloperServiceGrpcKt.DeveloperServiceCorouti
}
}.build()
}
}

View File

@ -1,85 +0,0 @@
package kritor.service
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import io.grpc.Status
import io.grpc.StatusRuntimeException
import io.kritor.event.MessageEvent
import io.kritor.message.*
import qq.service.contact.longPeer
import qq.service.msg.ForwardMessageHelper
import qq.service.msg.MessageHelper
import qq.service.msg.toKritorResponseMessages
internal object ForwardMessageService : ForwardMessageServiceGrpcKt.ForwardMessageServiceCoroutineImplBase() {
@Grpc("ForwardMessageService", "UploadForwardMessage")
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.chatType,
contact.longPeer().toString(),
contact.guildId,
request.messagesList
).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withCause(it))
}.getOrThrow()
return UploadForwardMessageResponse.newBuilder().apply {
this.resId = forwardMessage.resId
}.build()
}
@Grpc("ForwardMessageService", "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 ->
MessageEvent.newBuilder().apply {
this.time = detail.time
this.messageId = detail.qqMsgId
this.messageSeq = detail.msgSeq
this.contact = 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()
}
}

View File

@ -1,23 +1,33 @@
package kritor.service
import android.os.Bundle
import com.tencent.mobileqq.profilecard.api.IProfileCardBlacklistApi
import com.tencent.mobileqq.profilecard.api.IProfileProtocolConst
import com.tencent.mobileqq.profilecard.api.IProfileProtocolService
import com.tencent.mobileqq.qroute.QRoute
import io.grpc.Status
import io.grpc.StatusRuntimeException
import io.kritor.friend.*
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull
import qq.service.QQInterfaces
import qq.service.contact.ContactHelper
import qq.service.friend.FriendHelper
import kotlin.coroutines.resume
internal object FriendService: FriendServiceGrpcKt.FriendServiceCoroutineImplBase() {
internal object FriendService : FriendServiceGrpcKt.FriendServiceCoroutineImplBase() {
@Grpc("FriendService", "GetFriendList")
override suspend fun getFriendList(request: GetFriendListRequest): GetFriendListResponse {
val friendList = FriendHelper.getFriendList(if(request.hasRefresh()) request.refresh else false).onFailure {
throw StatusRuntimeException(Status.INTERNAL
val friendList = FriendHelper.getFriendList(if (request.hasRefresh()) request.refresh else false).onFailure {
throw StatusRuntimeException(
Status.INTERNAL
.withDescription(it.stackTraceToString())
)
}.getOrThrow()
return GetFriendListResponse.newBuilder().apply {
friendList.forEach {
this.addFriendList(FriendData.newBuilder().apply {
this.addFriendsInfo(FriendInfo.newBuilder().apply {
uin = it.uin.toLong()
uid = ContactHelper.getUidByUinAsync(uin)
qid = ""
@ -27,10 +37,208 @@ internal object FriendService: FriendServiceGrpcKt.FriendServiceCoroutineImplBas
level = 0
gender = it.gender.toInt()
groupId = it.groupid
ext = FriendExt.newBuilder().build().toByteString()
ext = ExtInfo.newBuilder().build()
})
}
}.build()
}
@Grpc("FriendService", "GetFriendProfileCard")
override suspend fun getFriendProfileCard(request: GetFriendProfileCardRequest): GetFriendProfileCardResponse {
return GetFriendProfileCardResponse.newBuilder().apply {
request.targetUinsList.forEach {
ContactHelper.getProfileCard(it).getOrThrow().let { info ->
addFriendsProfileCard(ProfileCard.newBuilder().apply {
this.uin = info.uin.toLong()
this.uid = ContactHelper.getUidByUinAsync(info.uin.toLong())
this.nick = info.strNick
this.remark = info.strReMark
this.level = info.iQQLevel
this.birthday = info.lBirthday
this.loginDay = info.lLoginDays.toInt()
this.voteCnt = info.lVoteCount.toInt()
this.qid = info.qid ?: ""
this.isSchoolVerified = info.schoolVerifiedFlag
this.ext = ExtInfo.newBuilder().apply {
this.bigVip = info.bBigClubVipOpen == 1.toByte()
this.hollywoodVip = info.bHollywoodVipOpen == 1.toByte()
this.qqVip = info.bQQVipOpen == 1.toByte()
this.superVip = info.bSuperQQOpen == 1.toByte()
this.voted = info.bVoted == 1.toByte()
}.build()
}.build())
}
}
request.targetUidsList.forEach {
ContactHelper.getProfileCard(ContactHelper.getUinByUidAsync(it).toLong()).getOrThrow().let { info ->
addFriendsProfileCard(ProfileCard.newBuilder().apply {
this.uin = info.uin.toLong()
this.uid = it
this.nick = info.strNick
this.remark = info.strReMark
this.level = info.iQQLevel
this.birthday = info.lBirthday
this.loginDay = info.lLoginDays.toInt()
this.voteCnt = info.lVoteCount.toInt()
this.qid = info.qid ?: ""
this.isSchoolVerified = info.schoolVerifiedFlag
this.ext = ExtInfo.newBuilder().apply {
this.bigVip = info.bBigClubVipOpen == 1.toByte()
this.hollywoodVip = info.bHollywoodVipOpen == 1.toByte()
this.qqVip = info.bQQVipOpen == 1.toByte()
this.superVip = info.bSuperQQOpen == 1.toByte()
this.voted = info.bVoted == 1.toByte()
}.build()
}.build())
}
}
}.build()
}
@Grpc("FriendService", "GetStrangerProfileCard")
override suspend fun getStrangerProfileCard(request: GetStrangerProfileCardRequest): GetStrangerProfileCardResponse {
return GetStrangerProfileCardResponse.newBuilder().apply {
request.targetUinsList.forEach {
ContactHelper.refreshAndGetProfileCard(it).getOrThrow().let { info ->
addStrangersProfileCard(ProfileCard.newBuilder().apply {
this.uin = info.uin.toLong()
this.uid = ContactHelper.getUidByUinAsync(info.uin.toLong())
this.nick = info.strNick
this.level = info.iQQLevel
this.birthday = info.lBirthday
this.loginDay = info.lLoginDays.toInt()
this.voteCnt = info.lVoteCount.toInt()
this.qid = info.qid ?: ""
this.isSchoolVerified = info.schoolVerifiedFlag
this.ext = ExtInfo.newBuilder().apply {
this.bigVip = info.bBigClubVipOpen == 1.toByte()
this.hollywoodVip = info.bHollywoodVipOpen == 1.toByte()
this.qqVip = info.bQQVipOpen == 1.toByte()
this.superVip = info.bSuperQQOpen == 1.toByte()
this.voted = info.bVoted == 1.toByte()
}.build()
}.build())
}
}
request.targetUidsList.forEach {
ContactHelper.refreshAndGetProfileCard(ContactHelper.getUinByUidAsync(it).toLong()).getOrThrow()
.let { info ->
addStrangersProfileCard(ProfileCard.newBuilder().apply {
this.uin = info.uin.toLong()
this.uid = it
this.nick = info.strNick
this.level = info.iQQLevel
this.birthday = info.lBirthday
this.loginDay = info.lLoginDays.toInt()
this.voteCnt = info.lVoteCount.toInt()
this.qid = info.qid ?: ""
this.isSchoolVerified = info.schoolVerifiedFlag
this.ext = ExtInfo.newBuilder().apply {
this.bigVip = info.bBigClubVipOpen == 1.toByte()
this.hollywoodVip = info.bHollywoodVipOpen == 1.toByte()
this.qqVip = info.bQQVipOpen == 1.toByte()
this.superVip = info.bSuperQQOpen == 1.toByte()
this.voted = info.bVoted == 1.toByte()
}.build()
}.build())
}
}
}.build()
}
@Grpc("FriendService", "SetProfileCard")
override suspend fun setProfileCard(request: SetProfileCardRequest): SetProfileCardResponse {
val bundle = Bundle()
val service = QQInterfaces.app
.getRuntimeService(IProfileProtocolService::class.java, "all")
if (request.hasNickName()) {
bundle.putString(IProfileProtocolConst.KEY_NICK, request.nickName)
}
if (request.hasCompany()) {
bundle.putString(IProfileProtocolConst.KEY_COMPANY, request.company)
}
if (request.hasEmail()) {
bundle.putString(IProfileProtocolConst.KEY_EMAIL, request.email)
}
if (request.hasCollege()) {
bundle.putString(IProfileProtocolConst.KEY_COLLEGE, request.college)
}
if (request.hasPersonalNote()) {
bundle.putString(IProfileProtocolConst.KEY_PERSONAL_NOTE, request.personalNote)
}
if (request.hasBirthday()) {
bundle.putInt(IProfileProtocolConst.KEY_BIRTHDAY, request.birthday)
}
if (request.hasAge()) {
bundle.putInt(IProfileProtocolConst.KEY_AGE, request.age)
}
service.setProfileDetail(bundle)
return super.setProfileCard(request)
}
@Grpc("FriendService", "IsBlackListUser")
override suspend fun isBlackListUser(request: IsBlackListUserRequest): IsBlackListUserResponse {
val uin = when (request.targetCase!!) {
IsBlackListUserRequest.TargetCase.TARGET_UIN -> request.targetUin.toString()
IsBlackListUserRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid)
IsBlackListUserRequest.TargetCase.TARGET_NOT_SET -> throw StatusRuntimeException(
Status.INVALID_ARGUMENT
.withDescription("account not set")
)
}
val blacklistApi = QRoute.api(IProfileCardBlacklistApi::class.java)
val isBlack = withTimeoutOrNull(5000) {
suspendCancellableCoroutine { continuation ->
blacklistApi.isBlackOrBlackedUin(uin) {
continuation.resume(it)
}
}
} ?: false
return IsBlackListUserResponse.newBuilder().setIsBlackListUser(isBlack).build()
}
@Grpc("FriendService", "VoteUser")
override suspend fun voteUser(request: VoteUserRequest): VoteUserResponse {
ContactHelper.voteUser(
when (request.targetCase!!) {
VoteUserRequest.TargetCase.TARGET_UIN -> request.targetUin
VoteUserRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
VoteUserRequest.TargetCase.TARGET_NOT_SET -> throw StatusRuntimeException(
Status.INVALID_ARGUMENT
.withDescription("account not set")
)
}, request.voteCount
).onFailure {
throw StatusRuntimeException(
Status.INTERNAL
.withDescription(it.stackTraceToString())
)
}
return VoteUserResponse.newBuilder().build()
}
@Grpc("FriendService", "GetUidByUin")
override suspend fun getUidByUin(request: GetUidByUinRequest): GetUidByUinResponse {
return GetUidByUinResponse.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

@ -17,7 +17,7 @@ import qq.service.file.GroupFileHelper.getGroupFileSystemInfo
import tencent.im.oidb.cmd0x6d6.oidb_0x6d6
import tencent.im.oidb.oidb_sso
internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCoroutineImplBase() {
internal object GroupFileService : GroupFileServiceGrpcKt.GroupFileServiceCoroutineImplBase() {
@Grpc("GroupFileService", "CreateFolder")
override suspend fun createFolder(request: CreateFolderRequest): CreateFolderResponse {
val data = Oidb0x6d7ReqBody(
@ -49,13 +49,15 @@ internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCorouti
@Grpc("GroupFileService", "DeleteFolder")
override suspend fun deleteFolder(request: DeleteFolderRequest): DeleteFolderResponse {
val fromServiceMsg = QQInterfaces.sendOidbAW("OidbSvc.0x6d7_1", 1751, 1, Oidb0x6d7ReqBody(
val fromServiceMsg = QQInterfaces.sendOidbAW(
"OidbSvc.0x6d7_1", 1751, 1, Oidb0x6d7ReqBody(
deleteFolder = DeleteFolderReq(
groupCode = request.groupId.toULong(),
appId = 3u,
folderId = request.folderId
)
).toByteArray()) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
).toByteArray()
) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
if (fromServiceMsg.wupBuffer == null) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
}
@ -97,14 +99,16 @@ internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCorouti
@Grpc("GroupFileService", "RenameFolder")
override suspend fun renameFolder(request: RenameFolderRequest): RenameFolderResponse {
val fromServiceMsg = QQInterfaces.sendOidbAW("OidbSvc.0x6d7_3", 1751, 3, Oidb0x6d7ReqBody(
val fromServiceMsg = QQInterfaces.sendOidbAW(
"OidbSvc.0x6d7_3", 1751, 3, Oidb0x6d7ReqBody(
renameFolder = RenameFolderReq(
groupCode = request.groupId.toULong(),
appId = 3u,
folderId = request.folderId,
folderName = request.name
)
).toByteArray()) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
).toByteArray()
) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
if (fromServiceMsg.wupBuffer == null) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
}
@ -122,17 +126,11 @@ internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCorouti
return getGroupFileSystemInfo(request.groupId)
}
@Grpc("GroupFileService", "GetRootFiles")
override suspend fun getRootFiles(request: GetRootFilesRequest): GetRootFilesResponse {
return GetRootFilesResponse.newBuilder().apply {
val response = GroupFileHelper.getGroupFiles(request.groupId)
this.addAllFiles(response.filesList)
this.addAllFolders(response.foldersList)
}.build()
}
@Grpc("GroupFileService", "GetFiles")
override suspend fun getFiles(request: GetFilesRequest): GetFilesResponse {
return GroupFileHelper.getGroupFiles(request.groupId, request.folderId)
@Grpc("GroupFileService", "GetFileList")
override suspend fun getFileList(request: GetFileListRequest): GetFileListResponse {
return if (request.hasFolderId())
GroupFileHelper.getGroupFiles(request.groupId, request.folderId)
else
GroupFileHelper.getGroupFiles(request.groupId)
}
}

View File

@ -3,6 +3,7 @@ package kritor.service
import io.grpc.Status
import io.grpc.StatusRuntimeException
import io.kritor.group.*
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.helper.TroopHonorHelper.decodeHonor
import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
import qq.service.contact.ContactHelper
@ -212,10 +213,11 @@ internal object GroupService : GroupServiceGrpcKt.GroupServiceCoroutineImplBase(
}.getOrThrow()
return GetGroupListResponse.newBuilder().apply {
groupList.forEach { groupInfo ->
this.addGroupInfo(GroupInfo.newBuilder().apply {
groupId = groupInfo.troopcode.toLong()
this.addGroupsInfo(GroupInfo.newBuilder().apply {
groupId = groupInfo.troopcode.ifNullOrEmpty { groupInfo.uin }.ifNullOrEmpty { groupInfo.troopuin }?.toLong() ?: 0
groupName = groupInfo.troopname.ifNullOrEmpty { groupInfo.troopRemark }
.ifNullOrEmpty { groupInfo.newTroopName } ?: ""
.ifNullOrEmpty { groupInfo.newTroopName }
?: ""
groupRemark = groupInfo.troopRemark ?: ""
owner = groupInfo.troopowneruin?.toLong() ?: 0
addAllAdmins(GroupHelper.getAdminList(groupId))
@ -231,8 +233,8 @@ internal object GroupService : GroupServiceGrpcKt.GroupServiceCoroutineImplBase(
override suspend fun getGroupMemberInfo(request: GetGroupMemberInfoRequest): GetGroupMemberInfoResponse {
val memberInfo = GroupHelper.getTroopMemberInfoByUin(
request.groupId.toString(), when (request.targetCase!!) {
GetGroupMemberInfoRequest.TargetCase.UIN -> request.uin
GetGroupMemberInfoRequest.TargetCase.UID -> ContactHelper.getUinByUidAsync(request.uid).toLong()
GetGroupMemberInfoRequest.TargetCase.TARGET_UID -> request.targetUin
GetGroupMemberInfoRequest.TargetCase.TARGET_UIN -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
else -> throw StatusRuntimeException(
Status.INVALID_ARGUMENT
.withDescription("target not set")
@ -246,8 +248,8 @@ internal object GroupService : GroupServiceGrpcKt.GroupServiceCoroutineImplBase(
return GetGroupMemberInfoResponse.newBuilder().apply {
groupMemberInfo = GroupMemberInfo.newBuilder().apply {
uid =
if (request.targetCase == GetGroupMemberInfoRequest.TargetCase.UID) request.uid else ContactHelper.getUidByUinAsync(
request.uin
if (request.targetCase == GetGroupMemberInfoRequest.TargetCase.TARGET_UID) request.targetUid else ContactHelper.getUidByUinAsync(
request.targetUin
)
uin = memberInfo.memberuin?.toLong() ?: 0
nick = memberInfo.troopnick
@ -264,7 +266,7 @@ internal object GroupService : GroupServiceGrpcKt.GroupServiceCoroutineImplBase(
shutUpTimestamp = memberInfo.gagTimeStamp
distance = memberInfo.distance
addAllHonor((memberInfo.honorList ?: "")
addAllHonors((memberInfo.honorList ?: "")
.split("|")
.filter { it.isNotBlank() }
.map { it.toInt() })
@ -286,7 +288,7 @@ internal object GroupService : GroupServiceGrpcKt.GroupServiceCoroutineImplBase(
}.getOrThrow()
return GetGroupMemberListResponse.newBuilder().apply {
memberList.forEach { memberInfo ->
this.addGroupMemberInfo(GroupMemberInfo.newBuilder().apply {
this.addGroupMembersInfo(GroupMemberInfo.newBuilder().apply {
uid = ContactHelper.getUidByUinAsync(memberInfo.memberuin?.toLong() ?: 0)
uin = memberInfo.memberuin?.toLong() ?: 0
nick = memberInfo.troopnick
@ -303,7 +305,7 @@ internal object GroupService : GroupServiceGrpcKt.GroupServiceCoroutineImplBase(
shutUpTimestamp = memberInfo.gagTimeStamp
distance = memberInfo.distance
addAllHonor((memberInfo.honorList ?: "")
addAllHonors((memberInfo.honorList ?: "")
.split("|")
.filter { it.isNotBlank() }
.map { it.toInt() })
@ -323,7 +325,7 @@ internal object GroupService : GroupServiceGrpcKt.GroupServiceCoroutineImplBase(
}.getOrThrow()
return GetProhibitedUserListResponse.newBuilder().apply {
prohibitedList.forEach {
this.addProhibitedUserInfo(ProhibitedUserInfo.newBuilder().apply {
this.addProhibitedUsersInfo(ProhibitedUserInfo.newBuilder().apply {
uid = ContactHelper.getUidByUinAsync(it.memberUin)
uin = it.memberUin
prohibitedTime = it.shutuptimestap
@ -380,7 +382,7 @@ internal object GroupService : GroupServiceGrpcKt.GroupServiceCoroutineImplBase(
.map { it.toInt() }.forEach {
val honor = decodeHonor(member.memberuin.toLong(), it, member.mHonorRichFlag)
if (honor != null) {
addGroupHonorInfo(GroupHonorInfo.newBuilder().apply {
addGroupHonorsInfo(GroupHonorInfo.newBuilder().apply {
uid = ContactHelper.getUidByUinAsync(member.memberuin.toLong())
uin = member.memberuin.toLong()
nick = member.troopnick

View File

@ -7,7 +7,7 @@ import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
import com.tencent.qqnt.msg.api.IMsgService
import io.grpc.Status
import io.grpc.StatusRuntimeException
import io.kritor.event.MessageEvent
import io.kritor.common.*
import io.kritor.message.*
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull
@ -21,9 +21,9 @@ import protobuf.message.routing.Grp
import qq.service.QQInterfaces
import qq.service.contact.longPeer
import qq.service.internals.NTServiceFetcher
import qq.service.msg.*
import qq.service.msg.ForwardMessageHelper
import qq.service.msg.MessageHelper
import qq.service.msg.NtMsgConvertor
import qq.service.msg.toKritorReqMessages
import kotlin.coroutines.resume
import kotlin.random.Random
import kotlin.random.nextUInt
@ -54,7 +54,7 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp
uniseq
).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withCause(it))
}.getOrThrow()
}.getOrThrow().toString()
}.build()
}
@ -88,8 +88,8 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp
return SendMessageByResIdResponse.newBuilder().build()
}
@Grpc("MessageService", "ClearMessages")
override suspend fun clearMessages(request: ClearMessagesRequest): ClearMessagesResponse {
@Grpc("MessageService", "SetMessageReaded")
override suspend fun setMessageReaded(request: SetMessageReadRequest): SetMessageReadResponse {
val contact = request.contact
val kernelService = NTServiceFetcher.kernelService
val sessionService = kernelService.wrapperSession
@ -104,7 +104,7 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp
Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene"))
}
service.clearMsgRecords(Contact(chatType, contact.peer, contact.subPeer), null)
return ClearMessagesResponse.newBuilder().build()
return SetMessageReadResponse.newBuilder().build()
}
@Grpc("MessageService", "RecallMessage")
@ -125,7 +125,7 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp
val kernelService = NTServiceFetcher.kernelService
val sessionService = kernelService.wrapperSession
val service = sessionService.msgService
service.recallMsg(contact, arrayListOf(request.messageId)) { code, msg ->
service.recallMsg(contact, arrayListOf(request.messageId.toLong())) { code, msg ->
if (code != 0) {
LogCenter.log("消息撤回失败: $code:$msg", Level.WARN)
}
@ -152,7 +152,7 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp
val msg: MsgRecord = withTimeoutOrNull(5000) {
val service = QRoute.api(IMsgService::class.java)
suspendCancellableCoroutine { continuation ->
service.getMsgsByMsgId(contact, arrayListOf(request.messageId)) { code, _, msgRecords ->
service.getMsgsByMsgId(contact, arrayListOf(request.messageId.toLong())) { code, _, msgRecords ->
if (code == 0 && msgRecords.isNotEmpty()) {
continuation.resume(msgRecords.first())
} else {
@ -166,13 +166,13 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp
} ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Message not found"))
return GetMessageResponse.newBuilder().apply {
this.message = MessageEvent.newBuilder().apply {
this.messageId = msg.msgId
this.message = PushMessageBody.newBuilder().apply {
this.messageId = msg.msgId.toString()
this.contact = request.contact
this.sender = Sender.newBuilder().apply {
this.uid = msg.senderUid ?: ""
this.uin = msg.senderUin
this.nick = msg.sendNickName ?: ""
this.uid = msg.senderUid ?: ""
}.build()
this.messageSeq = msg.msgSeq
this.addAllElements(msg.elements.toKritorReqMessages(contact))
@ -212,8 +212,8 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp
} ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Message not found"))
return GetMessageBySeqResponse.newBuilder().apply {
this.message = MessageEvent.newBuilder().apply {
this.messageId = msg.msgId
this.message = PushMessageBody.newBuilder().apply {
this.messageId = msg.msgId.toString()
this.contact = request.contact
this.sender = Sender.newBuilder().apply {
this.uin = msg.senderUin
@ -244,7 +244,7 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp
val msgs: List<MsgRecord> = withTimeoutOrNull(5000) {
val service = QRoute.api(IMsgService::class.java)
suspendCancellableCoroutine { continuation ->
service.getMsgs(contact, request.startMessageId, request.count, true) { code, _, msgRecords ->
service.getMsgs(contact, request.startMessageId.toLong(), request.count, true) { code, _, msgRecords ->
if (code == 0 && msgRecords.isNotEmpty()) {
continuation.resume(msgRecords)
} else {
@ -259,8 +259,8 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp
return GetHistoryMessageResponse.newBuilder().apply {
msgs.forEach {
addMessages(MessageEvent.newBuilder().apply {
this.messageId = it.msgId
addMessages(PushMessageBody.newBuilder().apply {
this.messageId = it.msgId.toString()
this.contact = request.contact
this.sender = Sender.newBuilder().apply {
this.uin = it.senderUin
@ -274,13 +274,83 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp
}.build()
}
@Grpc("MessageService", "DeleteEssenceMsg")
override suspend fun deleteEssenceMsg(request: DeleteEssenceMsgRequest): DeleteEssenceMsgResponse {
@Grpc("MessageService", "UploadForwardMessage")
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 msg: MsgRecord = withTimeoutOrNull(5000) {
val service = QRoute.api(IMsgService::class.java)
suspendCancellableCoroutine { continuation ->
service.getMsgsByMsgId(contact, arrayListOf(request.messageId)) { code, _, msgRecords ->
service.getMsgsByMsgId(contact, arrayListOf(request.messageId.toLong())) { code, _, msgRecords ->
if (code == 0 && msgRecords.isNotEmpty()) {
continuation.resume(msgRecords.first())
} else {
@ -294,17 +364,17 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp
} ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Message not found"))
if (MessageHelper.deleteEssenceMessage(request.groupId, msg.msgSeq, msg.msgRandom) == null)
throw StatusRuntimeException(Status.NOT_FOUND.withDescription("delete essence message failed"))
return DeleteEssenceMsgResponse.newBuilder().build()
return DeleteEssenceMessageResponse.newBuilder().build()
}
@Grpc("MessageService", "GetEssenceMessages")
override suspend fun getEssenceMessages(request: GetEssenceMessagesRequest): GetEssenceMessagesResponse {
@Grpc("MessageService", "GetEssenceMessageList")
override suspend fun getEssenceMessageList(request: GetEssenceMessageListRequest): GetEssenceMessageListResponse {
val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, request.groupId.toString())
return GetEssenceMessagesResponse.newBuilder().apply {
return GetEssenceMessageListResponse.newBuilder().apply {
MessageHelper.getEssenceMessageList(request.groupId, request.page, request.pageSize).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withCause(it))
}.getOrThrow().forEach {
addEssenceMessage(EssenceMessage.newBuilder().apply {
addMessages(EssenceMessageBody.newBuilder().apply {
withTimeoutOrNull(5000) {
val service = QRoute.api(IMsgService::class.java)
suspendCancellableCoroutine { continuation ->
@ -320,10 +390,10 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp
}
}
}?.let {
this.messageId = it.msgId
this.messageId = it.msgId.toString()
}
this.messageSeq = it.messageSeq
this.msgTime = it.senderTime.toInt()
this.messageTime = it.senderTime.toInt()
this.senderNick = it.senderNick
this.senderUin = it.senderId
this.operationTime = it.operatorTime.toInt()
@ -341,7 +411,7 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp
val msg: MsgRecord = withTimeoutOrNull(5000) {
val service = QRoute.api(IMsgService::class.java)
suspendCancellableCoroutine { continuation ->
service.getMsgsByMsgId(contact, arrayListOf(request.messageId)) { code, _, msgRecords ->
service.getMsgsByMsgId(contact, arrayListOf(request.messageId.toLong())) { code, _, msgRecords ->
if (code == 0 && msgRecords.isNotEmpty()) {
continuation.resume(msgRecords.first())
} else {
@ -359,8 +429,8 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp
return SetEssenceMessageResponse.newBuilder().build()
}
@Grpc("MessageService", "SetMessageCommentEmoji")
override suspend fun setMessageCommentEmoji(request: SetMessageCommentEmojiRequest): SetMessageCommentEmojiResponse {
@Grpc("MessageService", "ReactMessageWithEmoji")
override suspend fun reactMessageWithEmoji(request: ReactMessageWithEmojiRequest): ReactMessageWithEmojiResponse {
val contact = request.contact.let {
MessageHelper.generateContact(
when (it.scene!!) {
@ -377,7 +447,7 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp
val msg: MsgRecord = withTimeoutOrNull(5000) {
val service = QRoute.api(IMsgService::class.java)
suspendCancellableCoroutine { continuation ->
service.getMsgsByMsgId(contact, arrayListOf(request.messageId)) { code, _, msgRecords ->
service.getMsgsByMsgId(contact, arrayListOf(request.messageId.toLong())) { code, _, msgRecords ->
if (code == 0 && msgRecords.isNotEmpty()) {
continuation.resume(msgRecords.first())
} else {
@ -393,8 +463,8 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp
request.contact.longPeer(),
msg.msgSeq.toULong(),
request.faceId.toString(),
request.isComment
request.isSet
)
return SetMessageCommentEmojiResponse.newBuilder().build()
return ReactMessageWithEmojiResponse.newBuilder().build()
}
}

View File

@ -0,0 +1,33 @@
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()
}
@Grpc("QsignService", "GetCmdWhitelist")
override suspend fun getCmdWhitelist(request: GetCmdWhitelistRequest): GetCmdWhitelistResponse {
return GetCmdWhitelistResponse.newBuilder().apply {
addAllCommands(FEKit.getInstance().cmdWhiteList)
}.build()
}
}

View File

@ -14,7 +14,10 @@ import moe.fuqiuluo.shamrock.tools.toast
import moe.fuqiuluo.shamrock.xposed.helper.AppTalker
import mqq.app.MobileQQ
import java.io.File
import java.util.Calendar
import java.util.Date
import java.util.Timer
import java.util.TimerTask
internal enum class Level(
val id: Byte
@ -31,7 +34,29 @@ internal object LogCenter {
// 格式化时间
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 {
if (it.exists()) it.delete()
it.mkdirs()
@ -49,8 +74,6 @@ internal object LogCenter {
return@let result
}
private val format = SimpleDateFormat("[HH:mm:ss] ")
fun log(string: String, level: Level = Level.INFO, toast: Boolean = false) {
if (!ShamrockConfig[DebugMode] && level == Level.DEBUG) {
return

View File

@ -5,32 +5,35 @@ package moe.fuqiuluo.shamrock.internals
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
import io.kritor.event.*
import io.kritor.message.Contact
import io.kritor.message.Sender
import io.kritor.common.PushMessageBody
import io.kritor.common.Contact
import io.kritor.common.Scene
import io.kritor.common.Sender
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import qq.service.QQInterfaces
import qq.service.contact.ContactHelper
import qq.service.msg.toKritorEventMessages
internal object GlobalEventTransmitter : QQInterfaces() {
private val MessageEventFlow by lazy {
MutableSharedFlow<Pair<MsgRecord, MessageEvent>>()
MutableSharedFlow<Pair<MsgRecord, PushMessageBody>>()
}
private val noticeEventFlow by lazy {
MutableSharedFlow<NoticeEvent>()
}
private val requestEventFlow by lazy {
MutableSharedFlow<RequestsEvent>()
MutableSharedFlow<RequestEvent>()
}
private suspend fun pushNotice(noticeEvent: NoticeEvent) = noticeEventFlow.emit(noticeEvent)
private suspend fun pushRequest(requestEvent: RequestsEvent) = requestEventFlow.emit(requestEvent)
private suspend fun pushRequest(requestEvent: RequestEvent) = requestEventFlow.emit(requestEvent)
private suspend fun transMessageEvent(record: MsgRecord, message: MessageEvent) =
private suspend fun transMessageEvent(record: MsgRecord, message: PushMessageBody) =
MessageEventFlow.emit(record to message)
object MessageTransmitter {
@ -38,12 +41,12 @@ internal object GlobalEventTransmitter : QQInterfaces() {
record: MsgRecord,
elements: ArrayList<MsgElement>,
): Boolean {
transMessageEvent(record, MessageEvent.newBuilder().apply {
transMessageEvent(record, PushMessageBody.newBuilder().apply {
this.time = record.msgTime.toInt()
this.messageId = record.msgId
this.messageId = record.msgId.toString()
this.messageSeq = record.msgSeq
this.contact = Contact.newBuilder().apply {
this.scene = scene
this.scene = Scene.GROUP
this.peer = record.peerUin.toString()
this.subPeer = record.peerUid
}.build()
@ -61,13 +64,13 @@ internal object GlobalEventTransmitter : QQInterfaces() {
record: MsgRecord,
elements: ArrayList<MsgElement>,
): Boolean {
transMessageEvent(record, MessageEvent.newBuilder().apply {
transMessageEvent(record, PushMessageBody.newBuilder().apply {
this.time = record.msgTime.toInt()
this.messageId = record.msgId
this.messageId = record.msgId.toString()
this.messageSeq = record.msgSeq
this.contact = Contact.newBuilder().apply {
this.scene = scene
this.peer = record.senderUin.toString()
this.scene = Scene.FRIEND
this.peer = record.senderUid
this.subPeer = record.senderUid
}.build()
this.sender = Sender.newBuilder().apply {
@ -86,13 +89,13 @@ internal object GlobalEventTransmitter : QQInterfaces() {
groupCode: Long,
fromNick: String,
): Boolean {
transMessageEvent(record, MessageEvent.newBuilder().apply {
transMessageEvent(record, PushMessageBody.newBuilder().apply {
this.time = record.msgTime.toInt()
this.messageId = record.msgId
this.messageId = record.msgId.toString()
this.messageSeq = record.msgSeq
this.contact = Contact.newBuilder().apply {
this.scene = scene
this.peer = record.senderUin.toString()
this.scene = if (groupCode > 0) Scene.STRANGER_FROM_GROUP else Scene.STRANGER
this.peer = record.senderUid
this.subPeer = groupCode.toString()
}.build()
this.sender = Sender.newBuilder().apply {
@ -109,12 +112,12 @@ internal object GlobalEventTransmitter : QQInterfaces() {
record: MsgRecord,
elements: ArrayList<MsgElement>,
): Boolean {
transMessageEvent(record, MessageEvent.newBuilder().apply {
transMessageEvent(record, PushMessageBody.newBuilder().apply {
this.time = record.msgTime.toInt()
this.messageId = record.msgId
this.messageId = record.msgId.toString()
this.messageSeq = record.msgSeq
this.contact = Contact.newBuilder().apply {
this.scene = scene
this.scene = Scene.GUILD
this.peer = record.guildId ?: ""
this.subPeer = record.channelId ?: ""
}.build()
@ -138,7 +141,8 @@ internal object GlobalEventTransmitter : QQInterfaces() {
*/
suspend fun transPrivateFileEvent(
msgTime: Long,
userId: Long,
senderUid: String,
senderUin: Long,
fileId: String,
fileSubId: String,
fileName: String,
@ -147,12 +151,13 @@ internal object GlobalEventTransmitter : QQInterfaces() {
url: String
): Boolean {
pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.FRIEND_FILE_COME
this.type = NoticeEvent.NoticeType.PRIVATE_FILE_UPLOADED
this.time = msgTime.toInt()
this.friendFileCome = FriendFileComeNotice.newBuilder().apply {
this.privateFileUploaded = PrivateFileUploadedNotice.newBuilder().apply {
this.fileId = fileId
this.fileName = fileName
this.operator = userId
this.operatorUid = senderUid
this.operatorUin = senderUin
this.fileSize = fileSize
this.expireTime = expireTime.toInt()
this.fileSubId = fileSubId
@ -167,7 +172,8 @@ internal object GlobalEventTransmitter : QQInterfaces() {
*/
suspend fun transGroupFileEvent(
msgTime: Long,
userId: Long,
senderUid: String,
senderUin: Long,
groupId: Long,
uuid: String,
fileName: String,
@ -176,16 +182,17 @@ internal object GlobalEventTransmitter : QQInterfaces() {
url: String
): Boolean {
pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_FILE_COME
this.type = NoticeEvent.NoticeType.GROUP_FILE_UPLOADED
this.time = msgTime.toInt()
this.groupFileCome = GroupFileComeNotice.newBuilder().apply {
this.groupFileUploaded = GroupFileUploadedNotice.newBuilder().apply {
this.groupId = groupId
this.operator = userId
this.operatorUid = senderUid
this.operatorUin = senderUin
this.fileId = uuid
this.fileName = fileName
this.fileSize = fileSize
this.biz = bizId
this.url = url
this.busId = bizId
this.fileUrl = url
}.build()
}.build())
return true
@ -199,19 +206,19 @@ internal object GlobalEventTransmitter : QQInterfaces() {
suspend fun transGroupSign(
time: Long,
target: Long,
action: String?,
rankImg: String?,
action: String,
rankImg: String,
groupCode: Long
): Boolean {
pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_SIGN
this.type = NoticeEvent.NoticeType.GROUP_SIGN_IN
this.time = time.toInt()
this.groupSign = GroupSignNotice.newBuilder().apply {
this.groupSignIn = GroupSignInNotice.newBuilder().apply {
this.groupId = groupCode
this.targetUid = ContactHelper.getUidByUinAsync(target)
this.targetUin = target
this.action = action ?: ""
this.suffix = ""
this.rankImage = rankImg ?: ""
this.action = action
this.rankImage = rankImg
}.build()
}.build())
return true
@ -221,20 +228,23 @@ internal object GlobalEventTransmitter : QQInterfaces() {
time: Long,
operator: Long,
target: Long,
action: String?,
suffix: String?,
actionImg: String?,
action: String,
suffix: String,
actionImg: String,
groupCode: Long
): Boolean {
pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_POKE
this.type = NoticeEvent.NoticeType.GROUP_POKE
this.time = time.toInt()
this.groupPoke = GroupPokeNotice.newBuilder().apply {
this.action = action ?: ""
this.target = target
this.operator = operator
this.suffix = suffix ?: ""
this.actionImage = actionImg ?: ""
this.groupId = groupCode
this.action = action
this.targetUid = ContactHelper.getUidByUinAsync(target)
this.targetUin = target
this.operatorUid = ContactHelper.getUidByUinAsync(operator)
this.operatorUin = operator
this.suffix = suffix
this.actionImage = actionImg
}.build()
}.build())
return true
@ -247,10 +257,10 @@ internal object GlobalEventTransmitter : QQInterfaces() {
groupCode: Long,
operator: Long,
operatorUid: String,
type: GroupMemberIncreasedType
type: GroupMemberIncreasedNotice.GroupMemberIncreasedType
): Boolean {
pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_MEMBER_INCREASE
this.type = NoticeEvent.NoticeType.GROUP_MEMBER_INCREASE
this.time = time.toInt()
this.groupMemberIncrease = GroupMemberIncreasedNotice.newBuilder().apply {
this.groupId = groupCode
@ -271,10 +281,10 @@ internal object GlobalEventTransmitter : QQInterfaces() {
groupCode: Long,
operator: Long,
operatorUid: String,
type: GroupMemberDecreasedType
type: GroupMemberDecreasedNotice.GroupMemberDecreasedType
): Boolean {
pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_MEMBER_INCREASE
this.type = NoticeEvent.NoticeType.GROUP_MEMBER_DECREASE
this.time = time.toInt()
this.groupMemberDecrease = GroupMemberDecreasedNotice.newBuilder().apply {
this.groupId = groupCode
@ -296,7 +306,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
setAdmin: Boolean
): Boolean {
pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_ADMIN_CHANGED
this.type = NoticeEvent.NoticeType.GROUP_ADMIN_CHANGED
this.time = msgTime.toInt()
this.groupAdminChanged = GroupAdminChangedNotice.newBuilder().apply {
this.groupId = groupCode
@ -310,17 +320,19 @@ internal object GlobalEventTransmitter : QQInterfaces() {
suspend fun transGroupWholeBan(
msgTime: Long,
operator: Long,
groupCode: Long,
operatorUid: String,
operator: Long,
isOpen: Boolean
): Boolean {
pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_WHOLE_BAN
this.type = NoticeEvent.NoticeType.GROUP_WHOLE_BAN
this.time = msgTime.toInt()
this.groupWholeBan = GroupWholeBanNotice.newBuilder().apply {
this.groupId = groupCode
this.isWholeBan = isOpen
this.operator = operator
this.isBan = isOpen
this.operatorUid = operatorUid
this.operatorUin = operator
}.build()
}.build())
return true
@ -336,17 +348,17 @@ internal object GlobalEventTransmitter : QQInterfaces() {
duration: Int
): Boolean {
pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_MEMBER_BANNED
this.type = NoticeEvent.NoticeType.GROUP_MEMBER_BAN
this.time = msgTime.toInt()
this.groupMemberBanned = GroupMemberBannedNotice.newBuilder().apply {
this.groupMemberBan = GroupMemberBanNotice.newBuilder().apply {
this.groupId = groupCode
this.operatorUid = operatorUid
this.operatorUin = operator
this.targetUid = targetUid
this.targetUin = target
this.duration = duration
this.type = if (duration > 0) GroupMemberBanType.BAN
else GroupMemberBanType.LIFT_BAN
this.type = if (duration > 0) GroupMemberBanNotice.GroupMemberBanType.BAN
else GroupMemberBanNotice.GroupMemberBanType.LIFT_BAN
}.build()
}.build())
return true
@ -363,7 +375,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
tipText: String
): Boolean {
pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_RECALL
this.type = NoticeEvent.NoticeType.GROUP_RECALL
this.time = time.toInt()
this.groupRecall = GroupRecallNotice.newBuilder().apply {
this.groupId = groupCode
@ -371,7 +383,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
this.operatorUin = operator
this.targetUid = targetUid
this.targetUin = target
this.messageId = msgId
this.messageId = msgId.toString()
this.tipText = tipText
}.build()
}.build())
@ -381,26 +393,34 @@ internal object GlobalEventTransmitter : QQInterfaces() {
suspend fun transCardChange(
time: Long,
targetId: Long,
oldCard: String,
newCard: String,
groupId: Long
): Boolean {
pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeEvent.NoticeType.GROUP_CARD_CHANGED
this.time = time.toInt()
this.groupCardChanged = GroupCardChangedNotice.newBuilder().apply {
this.groupId = groupId
this.targetUin = targetId
this.newCard = newCard
}.build()
}.build())
return true
}
suspend fun transTitleChange(
time: Long,
targetId: Long,
targetUin: Long,
title: String,
groupId: Long
): Boolean {
pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_MEMBER_UNIQUE_TITLE_CHANGED
this.type = NoticeEvent.NoticeType.GROUP_MEMBER_UNIQUE_TITLE_CHANGED
this.time = time.toInt()
this.groupMemberUniqueTitleChanged = GroupUniqueTitleChangedNotice.newBuilder().apply {
this.groupId = groupId
this.target = targetId
this.targetUid = ContactHelper.getUidByUinAsync(targetUin)
this.targetUin = targetUin
this.title = title
}.build()
}.build())
@ -416,14 +436,16 @@ internal object GlobalEventTransmitter : QQInterfaces() {
subType: UInt
): Boolean {
pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_ESSENCE_CHANGED
this.type = NoticeEvent.NoticeType.GROUP_ESSENCE_CHANGED
this.time = time.toInt()
this.groupEssenceChanged = EssenceMessageNotice.newBuilder().apply {
this.groupEssenceChanged = GroupEssenceMessageNotice.newBuilder().apply {
this.groupId = groupId
this.messageId = msgId
this.sender = senderUin
this.operator = operatorUin
this.subType = subType.toInt()
this.messageId = msgId.toString()
this.targetUid = ContactHelper.getUidByUinAsync(targetUin)
this.targetUin = senderUin
this.operatorUid = ContactHelper.getUidByUinAsync(operatorUin)
this.operatorUin = operatorUin
this.isSet = subType.toInt() == 1
}.build()
}.build())
return true
@ -437,18 +459,17 @@ internal object GlobalEventTransmitter : QQInterfaces() {
suspend fun transPrivatePoke(
msgTime: Long,
operator: Long,
target: Long,
action: String?,
suffix: String?,
actionImg: String?
): Boolean {
pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.FRIEND_POKE
this.type = NoticeEvent.NoticeType.PRIVATE_POKE
this.time = msgTime.toInt()
this.friendPoke = FriendPokeNotice.newBuilder().apply {
this.privatePoke = PrivatePokeNotice.newBuilder().apply {
this.action = action ?: ""
this.target = target
this.operator = operator
this.operatorUid = ContactHelper.getUidByUinAsync(operator)
this.operatorUin = operator
this.suffix = suffix ?: ""
this.actionImage = actionImg ?: ""
}.build()
@ -458,11 +479,11 @@ internal object GlobalEventTransmitter : QQInterfaces() {
suspend fun transPrivateRecall(time: Long, operator: Long, msgId: Long, tipText: String): Boolean {
pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.FRIEND_RECALL
this.type = NoticeEvent.NoticeType.PRIVATE_RECALL
this.time = time.toInt()
this.friendRecall = FriendRecallNotice.newBuilder().apply {
this.operator = operator
this.messageId = msgId
this.privateRecall = PrivateRecallNotice.newBuilder().apply {
this.operatorUin = operator
this.messageId = msgId.toString()
this.tipText = tipText
}.build()
}.build())
@ -475,14 +496,15 @@ internal object GlobalEventTransmitter : QQInterfaces() {
* 请求 通知器
*/
object RequestTransmitter {
suspend fun transFriendApp(time: Long, operator: Long, tipText: String, flag: String): Boolean {
pushRequest(RequestsEvent.newBuilder().apply {
this.type = RequestType.FRIEND_APPLY
suspend fun transFriendApp(time: Long, applierUid: String, operator: Long, tipText: String, flag: String): Boolean {
pushRequest(RequestEvent.newBuilder().apply {
this.type = RequestEvent.RequestType.FRIEND_APPLY
this.time = time.toInt()
this.requestId = flag
this.friendApply = FriendApplyRequest.newBuilder().apply {
this.applierUid = applierUid
this.applierUin = operator
this.message = tipText
this.flag = flag
}.build()
}.build())
return true
@ -490,30 +512,48 @@ internal object GlobalEventTransmitter : QQInterfaces() {
suspend fun transGroupApply(
time: Long,
applier: Long,
applierUin: Long,
applierUid: String,
reason: String,
groupCode: Long,
flag: String,
type: GroupApplyType
flag: String
): Boolean {
pushRequest(RequestsEvent.newBuilder().apply {
this.type = RequestType.GROUP_APPLY
pushRequest(RequestEvent.newBuilder().apply {
this.type = RequestEvent.RequestType.GROUP_APPLY
this.time = time.toInt()
this.requestId = flag
this.groupApply = GroupApplyRequest.newBuilder().apply {
this.applierUid = applierUid
this.applierUin = applier
this.applierUin = applierUin
this.groupId = groupCode
this.reason = reason
this.flag = flag
this.type = type
}.build()
}.build())
return true
}
suspend fun transGroupInvite(
time: Long,
inviterUid: String,
inviterUin: Long,
groupCode: Long,
flag: String
): Boolean {
pushRequest(RequestEvent.newBuilder().apply {
this.type = RequestEvent.RequestType.GROUP_APPLY
this.time = time.toInt()
this.requestId = flag
this.invitedGroup = InvitedJoinGroupRequest.newBuilder().apply {
this.inviterUid = inviterUid
this.inviterUin = inviterUin
this.groupId = groupCode
}.build()
}.build())
return true
}
}
suspend inline fun onMessageEvent(collector: FlowCollector<Pair<MsgRecord, MessageEvent>>) {
suspend inline fun onMessageEvent(collector: FlowCollector<Pair<MsgRecord, PushMessageBody>>) {
MessageEventFlow.collect {
GlobalScope.launch {
collector.emit(it)
@ -529,7 +569,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
}
}
suspend inline fun onRequestEvent(collector: FlowCollector<RequestsEvent>) {
suspend inline fun onRequestEvent(collector: FlowCollector<RequestEvent>) {
requestEventFlow.collect {
GlobalScope.launch {
collector.emit(it)

View File

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

View File

@ -9,11 +9,13 @@ import qq.service.QQInterfaces
object SwitchStatus: IInteract, QQInterfaces() {
override fun invoke(intent: Intent) {
if (app.isLogin) {
AppTalker.talk("switch_status") {
put("account", app.currentAccountUin)
put("nickname", if (app is QQAppInterface) app.currentNickname else "unknown")
put("nickname", if (app is QQAppInterface) (app.currentNickname ?: "unknown") else "unknown")
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.MsgConstant
import io.kritor.message.Scene
import io.kritor.common.Scene
suspend fun Contact.longPeer(): Long {
return when(this.chatType) {
@ -12,7 +12,7 @@ suspend fun Contact.longPeer(): Long {
}
}
suspend fun io.kritor.message.Contact.longPeer(): Long {
suspend fun io.kritor.common.Contact.longPeer(): Long {
return when(this.scene) {
Scene.GROUP -> peer.toLong()
Scene.FRIEND, Scene.STRANGER, Scene.STRANGER_FROM_GROUP -> if (peer.startsWith("u_")) ContactHelper.getUinByUidAsync(peer).toLong() else peer.toLong()

View File

@ -5,11 +5,7 @@ package qq.service.file
import com.tencent.mobileqq.pb.ByteStringMicro
import io.grpc.Status
import io.grpc.StatusRuntimeException
import io.kritor.file.File
import io.kritor.file.Folder
import io.kritor.file.GetFileSystemInfoResponse
import io.kritor.file.GetFilesRequest
import io.kritor.file.GetFilesResponse
import io.kritor.file.*
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY
@ -77,7 +73,7 @@ internal object GroupFileHelper: QQInterfaces() {
}.build()
}
suspend fun getGroupFiles(groupId: Long, folderId: String = "/"): GetFilesResponse {
suspend fun getGroupFiles(groupId: Long, folderId: String = "/"): GetFileListResponse {
val fileSystemInfo = getGroupFileSystemInfo(groupId)
val fromServiceMsg = sendOidbAW("OidbSvc.0x6d8_1", 1752, 1, oidb_0x6d8.ReqBody().also {
it.file_list_info_req.set(oidb_0x6d8.GetFileListReqBody().apply {
@ -121,7 +117,7 @@ internal object GroupFileHelper: QQInterfaces() {
this.fileSize = fileInfo.uint64_file_size.get()
this.busId = fileInfo.uint32_bus_id.get()
this.uploadTime = fileInfo.uint32_upload_time.get()
this.deadTime = fileInfo.uint32_dead_time.get()
this.expireTime = fileInfo.uint32_dead_time.get()
this.modifyTime = fileInfo.uint32_modify_time.get()
this.downloadTimes = fileInfo.uint32_download_times.get()
this.uploader = fileInfo.uint64_uploader_uin.get()
@ -150,7 +146,7 @@ internal object GroupFileHelper: QQInterfaces() {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to fetch oidb response"))
}
return GetFilesResponse.newBuilder().apply {
return GetFileListResponse.newBuilder().apply {
this.addAllFiles(files)
this.addAllFolders(folders)
}.build()

View File

@ -14,7 +14,7 @@ import qq.service.bdh.RichProtoSvc
import qq.service.kernel.SimpleKernelMsgListener
import qq.service.msg.MessageHelper
object AioListener: SimpleKernelMsgListener() {
object AioListener : SimpleKernelMsgListener() {
override fun onRecvMsg(records: ArrayList<MsgRecord>) {
records.forEach {
GlobalScope.launch {
@ -60,7 +60,12 @@ object AioListener: SimpleKernelMsgListener() {
LogCenter.log("私聊临时消息(private = ${record.senderUin}, groupId=$groupCode)")
if (!GlobalEventTransmitter.MessageTransmitter.transTempMessage(record, record.elements, groupCode, fromNick)
if (!GlobalEventTransmitter.MessageTransmitter.transTempMessage(
record,
record.elements,
groupCode,
fromNick
)
) {
LogCenter.log("私聊临时消息推送失败 -> MessageTransmitter", Level.WARN)
}
@ -92,7 +97,6 @@ object AioListener: SimpleKernelMsgListener() {
}
private suspend fun onC2CFileMsg(record: MsgRecord) {
val userId = record.senderUin
val fileMsg = record.elements.firstOrNull {
it.elementType == MsgConstant.KELEMTYPEFILE
}?.fileElement ?: kotlin.run {
@ -108,7 +112,7 @@ object AioListener: SimpleKernelMsgListener() {
val url = RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId)
if (!GlobalEventTransmitter.FileNoticeTransmitter
.transPrivateFileEvent(record.msgTime, userId, fileId, fileSubId, fileName, fileSize, expireTime, url)
.transPrivateFileEvent(record.msgTime, record.senderUid, record.senderUin, fileId, fileSubId, fileName, fileSize, expireTime, url)
) {
LogCenter.log("私聊文件消息推送失败 -> FileNoticeTransmitter", Level.WARN)
}
@ -116,7 +120,6 @@ object AioListener: SimpleKernelMsgListener() {
private suspend fun onGroupFileMsg(record: MsgRecord) {
val groupId = record.peerUin
val userId = record.senderUin
val fileMsg = record.elements.firstOrNull {
it.elementType == MsgConstant.KELEMTYPEFILE
}?.fileElement ?: kotlin.run {
@ -132,9 +135,15 @@ object AioListener: SimpleKernelMsgListener() {
val url = RichProtoSvc.getGroupFileDownUrl(record.peerUin, uuid, bizId)
if (!GlobalEventTransmitter.FileNoticeTransmitter
.transGroupFileEvent(record.msgTime, userId, groupId, uuid, fileName, fileSize, bizId, url)
.transGroupFileEvent(record.msgTime, record.senderUid, record.senderUin, groupId, uuid, fileName, fileSize, bizId, url)
) {
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.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.msg.api.IMsgService
import io.kritor.event.GroupApplyType
import io.kritor.event.GroupMemberDecreasedType
import io.kritor.event.GroupMemberIncreasedType
import io.kritor.event.GroupMemberDecreasedNotice.GroupMemberDecreasedType
import io.kritor.event.GroupMemberIncreasedNotice.GroupMemberIncreasedType
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@ -127,7 +126,7 @@ internal object PrimitiveListener {
LogCenter.log("私聊戳一戳: $operation $action $target $suffix")
if (!GlobalEventTransmitter.PrivateNoticeTransmitter
.transPrivatePoke(msgTime, operation.toLong(), target.toLong(), action, suffix, actionImg)
.transPrivatePoke(msgTime, operation.toLong(), action, suffix, actionImg)
) {
LogCenter.log("私聊戳一戳推送失败!", Level.WARN)
}
@ -162,7 +161,7 @@ internal object PrimitiveListener {
}
LogCenter.log("来自$applier 的好友申请:$msg ($source)")
if (!GlobalEventTransmitter.RequestTransmitter
.transFriendApp(msgTime, applier, msg, flag)
.transFriendApp(msgTime, applierUid, applier, msg, flag)
) {
LogCenter.log("好友申请推送失败!", Level.WARN)
}
@ -321,8 +320,8 @@ internal object PrimitiveListener {
it.key to it.value
}
val target = params["uin_str2"] ?: params["mqq_uin"] ?: return
val operation = params["uin_str1"] ?: return
val target = params["uin_str2"] ?: params["mqq_uin"] ?: ""
val operator = params["uin_str1"] ?: ""
val suffix = params["suffix_str"] ?: ""
val actionImg = params["action_img_url"] ?: ""
val action = params["alt_str1"]
@ -333,9 +332,9 @@ internal object PrimitiveListener {
when (detail.type) {
1061u -> {
LogCenter.log("群戳一戳($groupId): $operation $action $target $suffix")
LogCenter.log("群戳一戳($groupId): $operator $action $target $suffix")
if (!GlobalEventTransmitter.GroupNoticeTransmitter
.transGroupPoke(time, operation.toLong(), target.toLong(), action, suffix, actionImg, groupId)
.transGroupPoke(time, operator.toLong(), target.toLong(), action, suffix, actionImg, groupId)
) {
LogCenter.log("群戳一戳推送失败!", Level.WARN)
}
@ -507,7 +506,7 @@ internal object PrimitiveListener {
if (wholeBan) {
LogCenter.log("群全员禁言($groupCode): $operator -> ${if (rawDuration != 0) "开启" else "关闭"}")
if (!GlobalEventTransmitter.GroupNoticeTransmitter
.transGroupWholeBan(msgTime, groupCode, operator, rawDuration != 0)
.transGroupWholeBan(msgTime, groupCode, operatorUid, operator, rawDuration != 0)
) {
LogCenter.log("群禁言推送失败!", Level.WARN)
}
@ -595,7 +594,7 @@ internal object PrimitiveListener {
}
LogCenter.log("入群申请($groupCode) $applier: \"$reason\", seq: $msgSeq")
if (!GlobalEventTransmitter.RequestTransmitter
.transGroupApply(time, applier, applierUid, reason, groupCode, flag, GroupApplyType.GROUP_APPLY_ADD)
.transGroupApply(time, applier, applierUid, reason, groupCode, flag)
) {
LogCenter.log("入群申请推送失败!", Level.WARN)
}
@ -630,7 +629,7 @@ internal object PrimitiveListener {
}
LogCenter.log("邀请入群申请($groupCode): $applier")
if (!GlobalEventTransmitter.RequestTransmitter
.transGroupApply(time, applier, applierUid, "", groupCode, flag, GroupApplyType.GROUP_APPLY_ADD)
.transGroupApply(time, applier, applierUid, "", groupCode, flag)
) {
LogCenter.log("邀请入群申请推送失败!", Level.WARN)
}
@ -658,7 +657,7 @@ internal object PrimitiveListener {
"$time;$groupCode;$uin"
}
if (!GlobalEventTransmitter.RequestTransmitter
.transGroupApply(time, invitor, invitorUid, "", groupCode, flag, GroupApplyType.GROUP_APPLY_INVITE)
.transGroupInvite(time, invitorUid, invitor, groupCode, flag)
) {
LogCenter.log("邀请入群推送失败!", Level.WARN)
}

View File

@ -1,14 +1,15 @@
package qq.service.msg
import com.tencent.mobileqq.qroute.QRoute
import com.tencent.qqnt.kernel.nativeinterface.Contact
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
import com.tencent.qqnt.msg.api.IMsgService
import io.grpc.Status
import io.grpc.StatusRuntimeException
import io.kritor.message.ForwardElement
import io.kritor.message.ForwardMessageBody
import io.kritor.message.Scene
import io.kritor.common.ForwardElement
import io.kritor.common.ForwardMessageBody
import io.kritor.common.Scene
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.shamrock.helper.Level
@ -30,9 +31,7 @@ import kotlin.time.Duration.Companion.seconds
internal object ForwardMessageHelper : QQInterfaces() {
suspend fun uploadMultiMsg(
chatType: Int,
peerId: String,
fromId: String = peerId,
contact: Contact,
messages: List<ForwardMessageBody>,
): Result<ForwardElement> {
var i = -1
@ -41,20 +40,8 @@ internal object ForwardMessageHelper : QQInterfaces() {
val msgs = messages.mapNotNull { msg ->
kotlin.runCatching {
val contact = msg.contact.let {
MessageHelper.generateContact(
when (it.scene!!) {
Scene.GROUP -> MsgConstant.KCHATTYPEGROUP
Scene.FRIEND -> MsgConstant.KCHATTYPEC2C
Scene.GUILD -> MsgConstant.KCHATTYPEGUILD
Scene.STRANGER_FROM_GROUP -> MsgConstant.KCHATTYPETEMPC2CFROMGROUP
Scene.NEARBY -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
Scene.STRANGER -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene"))
}, it.peer, it.subPeer
)
}
if (msg.hasMessageId()) {
when (msg.forwardMessageCase) {
ForwardMessageBody.ForwardMessageCase.MESSAGE_ID -> {
val record: MsgRecord = withTimeoutOrNull(5000) {
val service = QRoute.api(IMsgService::class.java)
suspendCancellableCoroutine { continuation ->
@ -90,7 +77,7 @@ internal object ForwardMessageHelper : QQInterfaces() {
msgType = when (record.chatType) {
MsgConstant.KCHATTYPEC2C -> 9
MsgConstant.KCHATTYPEGROUP -> 82
else -> throw UnsupportedOperationException("Unsupported chatType: $chatType")
else -> throw UnsupportedOperationException("Unsupported chatType: ${contact.chatType}")
},
msgSubType = if (record.chatType == MsgConstant.KCHATTYPEC2C) 175 else null,
divSeq = if (record.chatType == MsgConstant.KCHATTYPEC2C) 175 else null,
@ -117,14 +104,17 @@ internal object ForwardMessageHelper : QQInterfaces() {
}.getOrThrow().second
)
)
} else {
}
ForwardMessageBody.ForwardMessageCase.MESSAGE -> {
val _msg = msg.message
PushMsgBody(
msgHead = if (msg.hasSender()) ResponseHead(
peer = if (msg.sender.hasUin()) msg.sender.uin else TicketHelper.getUin().toLong(),
peerUid = msg.sender.uid,
msgHead = if (_msg.hasSender()) ResponseHead(
peer = if (_msg.sender.hasUin()) _msg.sender.uin else TicketHelper.getUin().toLong(),
peerUid = _msg.sender.uid,
receiverUid = TicketHelper.getUid(),
forward = ResponseForward(
friendName = if (msg.sender.hasNick()) msg.sender.nick else TicketHelper.getNickname()
friendName = if (_msg.sender.hasNick()) _msg.sender.nick else TicketHelper.getNickname()
)
) else ResponseHead(
peer = TicketHelper.getUin().toLong(),
@ -139,12 +129,12 @@ internal object ForwardMessageHelper : QQInterfaces() {
msgSubType = 175,
divSeq = 175,
msgViaRandom = Random.nextLong(),
sequence = msg.messageSeq.toLong(),
msgTime = msg.messageTime.toLong(),
sequence = _msg.messageSeq,
msgTime = _msg.time.toLong(),
u2 = 1,
u6 = 0,
u7 = 0,
msgSeq = msg.messageSeq.toLong(),
msgSeq = _msg.messageSeq,
forwardHead = ForwardHead(
u1 = 0,
u2 = 0,
@ -154,15 +144,18 @@ internal object ForwardMessageHelper : QQInterfaces() {
)
),
body = MsgBody(
richText = msg.elementsList.toRichText(contact).onSuccess {
richText = _msg.elementsList.toRichText(contact).onSuccess {
desc[++i] =
(if (msg.hasSender() && msg.sender.hasNick()) msg.sender.nick else TicketHelper.getNickname()) + ": " + it.first
(if (_msg.hasSender() && _msg.sender.hasNick()) _msg.sender.nick else TicketHelper.getNickname()) + ": " + it.first
}.onFailure {
error("消息合成失败: ${it.stackTraceToString()}")
}.getOrThrow().second
)
)
}
else -> null
}
}.onFailure {
LogCenter.log("消息节点解析失败:${it.stackTraceToString()}", Level.WARN)
}.getOrNull()
@ -192,21 +185,21 @@ internal object ForwardMessageHelper : QQInterfaces() {
)
val req = LongMsgReq(
sendInfo = when (chatType) {
sendInfo = when (contact.chatType) {
MsgConstant.KCHATTYPEC2C -> SendLongMsgInfo(
type = 1,
uid = LongMsgUid(if (peerId.startsWith("u_")) peerId else ContactHelper.getUidByUinAsync(peerId.toLong())),
uid = LongMsgUid(contact.peerUid),
payload = DeflateTools.gzip(payload.toByteArray())
)
MsgConstant.KCHATTYPEGROUP -> SendLongMsgInfo(
type = 3,
uid = LongMsgUid(fromId),
groupUin = fromId.toULong(),
uid = LongMsgUid(contact.peerUid),
groupUin = contact.peerUid.toULong(),
payload = DeflateTools.gzip(payload.toByteArray())
)
else -> throw UnsupportedOperationException("Unsupported chatType: $chatType")
else -> throw UnsupportedOperationException("Unsupported chatType: ${contact.chatType}")
},
setting = LongMsgSettings(
field1 = 4,

View File

@ -1,11 +1,13 @@
package qq.service.msg
import com.google.protobuf.ByteString
import com.tencent.mobileqq.qroute.QRoute
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
import com.tencent.qqnt.msg.api.IMsgService
import io.kritor.message.*
import io.kritor.common.*
import io.kritor.common.Element.ElementType
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.shamrock.helper.ActionMsgException
@ -55,11 +57,13 @@ private object MsgConvertor {
val text = element.textElement
val elem = Element.newBuilder()
if (text.atType != MsgConstant.ATTYPEUNKNOWN) {
elem.type = ElementType.AT
elem.setAt(AtElement.newBuilder().apply {
this.uid = text.atNtUid
this.uin = ContactHelper.getUinByUidAsync(text.atNtUid).toLong()
})
} else {
elem.type = ElementType.TEXT
elem.setText(TextElement.newBuilder().apply {
this.text = text.content
})
@ -71,6 +75,7 @@ private object MsgConvertor {
val face = element.faceElement
val elem = Element.newBuilder()
if (face.faceType == 5) {
elem.type = ElementType.POKE
elem.setPoke(PokeElement.newBuilder().apply {
this.id = face.vaspokeId
this.type = face.pokeType
@ -78,30 +83,45 @@ private object MsgConvertor {
})
} else {
when (face.faceIndex) {
114 -> elem.setBasketball(BasketballElement.newBuilder().apply {
114 -> {
elem.type = ElementType.BASKETBALL
elem.setBasketball(BasketballElement.newBuilder().apply {
this.id = face.resultId.ifNullOrEmpty { "0" }?.toInt() ?: 0
})
}
358 -> elem.setDice(DiceElement.newBuilder().apply {
358 -> {
elem.type = ElementType.DICE
elem.setDice(DiceElement.newBuilder().apply {
this.id = face.resultId.ifNullOrEmpty { "0" }?.toInt() ?: 0
})
}
359 -> elem.setRps(RpsElement.newBuilder().apply {
359 -> {
elem.type = ElementType.RPS
elem.setRps(RpsElement.newBuilder().apply {
this.id = face.resultId.ifNullOrEmpty { "0" }?.toInt() ?: 0
})
}
394 -> elem.setFace(FaceElement.newBuilder().apply {
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.setFace(FaceElement.newBuilder().apply {
else -> {
elem.type = ElementType.FACE
elem.setFace(FaceElement.newBuilder().apply {
this.id = face.faceIndex
this.isBig = face.faceType == 3
})
}
}
}
return Result.success(elem.build())
}
@ -134,9 +154,10 @@ private object MsgConvertor {
LogCenter.log({ "receive image: $image" }, Level.DEBUG)
val elem = Element.newBuilder()
elem.type = ElementType.IMAGE
elem.setImage(ImageElement.newBuilder().apply {
this.file = md5
this.url = when (record.chatType) {
this.file = ByteString.copyFromUtf8(md5)
this.fileUrl = when (record.chatType) {
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(
originalUrl = originalUrl,
md5 = md5,
@ -175,7 +196,7 @@ private object MsgConvertor {
else -> throw UnsupportedOperationException("Not supported chat type: ${record.chatType}")
}
this.type =
if (image.isFlashPic == true) ImageType.FLASH else if (image.original) ImageType.ORIGIN else ImageType.COMMON
if (image.isFlashPic == true) ImageElement.ImageType.FLASH else if (image.original) ImageElement.ImageType.ORIGIN else ImageElement.ImageType.COMMON
this.subType = image.picSubType
})
@ -190,8 +211,9 @@ private object MsgConvertor {
ptt.fileName.substring(5)
else ptt.md5HexStr
elem.type = ElementType.VOICE
elem.setVoice(VoiceElement.newBuilder().apply {
this.url = when (record.chatType) {
this.fileUrl = when (record.chatType) {
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPttDownUrl("0", ptt.fileUuid)
MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupPttDownUrl(
"0",
@ -201,7 +223,7 @@ private object MsgConvertor {
else -> throw UnsupportedOperationException("Not supported chat type: ${record.chatType}")
}
this.file = md5
this.file = ByteString.copyFromUtf8(md5)
this.magic = ptt.voiceChangeType != MsgConstant.KPTTVOICECHANGETYPENONE
})
@ -218,9 +240,10 @@ private object MsgConvertor {
it[it.size - 2].hex2ByteArray()
}
} else video.fileName.split(".")[0].hex2ByteArray()
elem.type = ElementType.VIDEO
elem.setVideo(VideoElement.newBuilder().apply {
this.file = md5.toHexString()
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.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", md5, video.fileUuid)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid)
@ -233,6 +256,7 @@ private object MsgConvertor {
suspend fun convertMarketFace(record: MsgRecord, element: MsgElement): Result<Element> {
val marketFace = element.marketFaceElement
val elem = Element.newBuilder()
elem.type = ElementType.MARKET_FACE
elem.setMarketFace(MarketFaceElement.newBuilder().apply {
this.id = marketFace.emojiId.lowercase()
})
@ -245,6 +269,7 @@ private object MsgConvertor {
when (data["app"].asString) {
"com.tencent.multimsg" -> {
val info = data["meta"].asJsonObject["detail"].asJsonObject
elem.type = ElementType.FORWARD
elem.setForward(ForwardElement.newBuilder().apply {
this.resId = info["resid"].asString
this.uniseq = info["uniseq"].asString
@ -257,6 +282,7 @@ private object MsgConvertor {
"com.tencent.troopsharecard" -> {
val info = data["meta"].asJsonObject["contact"].asJsonObject
elem.type = ElementType.CONTACT
elem.setContact(ContactElement.newBuilder().apply {
this.scene = Scene.GROUP
this.peer = info["jumpUrl"].asString.split("group_code=")[1]
@ -265,6 +291,7 @@ private object MsgConvertor {
"com.tencent.contact.lua" -> {
val info = data["meta"].asJsonObject["contact"].asJsonObject
elem.type = ElementType.CONTACT
elem.setContact(ContactElement.newBuilder().apply {
this.scene = Scene.FRIEND
this.peer = info["jumpUrl"].asString.split("uin=")[1]
@ -273,6 +300,7 @@ private object MsgConvertor {
"com.tencent.map" -> {
val info = data["meta"].asJsonObject["Location.Search"].asJsonObject
elem.type = ElementType.LOCATION
elem.setLocation(LocationElement.newBuilder().apply {
this.lat = info["lat"].asString.toFloat()
this.lon = info["lng"].asString.toFloat()
@ -281,16 +309,20 @@ private object MsgConvertor {
})
}
else -> elem.setJson(JsonElement.newBuilder().apply {
else -> {
elem.type = ElementType.JSON
elem.setJson(JsonElement.newBuilder().apply {
this.json = data.toString()
})
}
}
return Result.success(elem.build())
}
suspend fun convertReply(record: MsgRecord, element: MsgElement): Result<Element> {
val reply = element.replyElement
val elem = Element.newBuilder()
elem.type = ElementType.REPLY
elem.setReply(ReplyElement.newBuilder().apply {
val msgSeq = reply.replayMsgSeq
val contact = MessageHelper.generateContact(record)
@ -304,9 +336,9 @@ private object MsgConvertor {
}
if (sourceRecords.isNullOrEmpty()) {
LogCenter.log("无法查询到回复的消息ID: seq = $msgSeq", Level.WARN)
this.messageId = reply.replayMsgId
this.messageId = reply.replayMsgId.toString()
} else {
this.messageId = sourceRecords.first().msgId
this.messageId = sourceRecords.first().msgId.toString()
}
})
return Result.success(elem.build())
@ -332,6 +364,7 @@ private object MsgConvertor {
else -> RichProtoSvc.getGroupFileDownUrl(record.peerUin, fileId, bizId)
}
val elem = Element.newBuilder()
elem.type = ElementType.FILE
elem.setFile(FileElement.newBuilder().apply {
this.name = fileName
this.size = fileSize
@ -347,6 +380,7 @@ private object MsgConvertor {
suspend fun convertMarkdown(record: MsgRecord, element: MsgElement): Result<Element> {
val markdown = element.markdownElement
val elem = Element.newBuilder()
elem.type = ElementType.MARKDOWN
elem.setMarkdown(MarkdownElement.newBuilder().apply {
this.markdown = markdown.content
})
@ -356,6 +390,7 @@ private object MsgConvertor {
suspend fun convertBubbleFace(record: MsgRecord, element: MsgElement): Result<Element> {
val bubbleFace = element.faceBubbleElement
val elem = Element.newBuilder()
elem.type = ElementType.BUBBLE_FACE
elem.setBubbleFace(BubbleFaceElement.newBuilder().apply {
this.id = bubbleFace.yellowFaceInfo.index
this.count = bubbleFace.faceCount ?: 1
@ -366,9 +401,10 @@ private object MsgConvertor {
suspend fun convertInlineKeyboard(record: MsgRecord, element: MsgElement): Result<Element> {
val inlineKeyboard = element.inlineKeyboardElement
val elem = Element.newBuilder()
elem.setButton(ButtonElement.newBuilder().apply {
elem.type = ElementType.KEYBOARD
elem.setKeyboard(KeyboardElement.newBuilder().apply {
inlineKeyboard.rows.forEach { row ->
this.addRows(ButtonRow.newBuilder().apply {
this.addRows(KeyboardRow.newBuilder().apply {
row.buttons.forEach buttonsLoop@{ button ->
if (button == null) return@buttonsLoop
this.addButtons(Button.newBuilder().apply {

View File

@ -4,7 +4,9 @@ package qq.service.msg
import com.tencent.qqnt.kernel.nativeinterface.Contact
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import io.kritor.message.*
import io.kritor.common.*
import io.kritor.common.Element.ElementType
import io.kritor.common.ImageElement.ImageType
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUInt
@ -62,9 +64,9 @@ suspend fun List<Elem>.toKritorResponseMessages(contact: Contact): ArrayList<Ele
kritorMessages.add(Element.newBuilder().apply {
this.type = ElementType.IMAGE
this.image = ImageElement.newBuilder().apply {
this.fileName = md5
this.fileMd5 = md5
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
@ -82,9 +84,9 @@ suspend fun List<Elem>.toKritorResponseMessages(contact: Contact): ArrayList<Ele
kritorMessages.add(Element.newBuilder().apply {
this.type = ElementType.IMAGE
this.image = ImageElement.newBuilder().apply {
this.fileName = md5
this.fileMd5 = md5
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
@ -112,7 +114,7 @@ suspend fun List<Elem>.toKritorResponseMessages(contact: Contact): ArrayList<Ele
kritorMessages.add(Element.newBuilder().apply {
this.type = ElementType.REPLY
this.reply = ReplyElement.newBuilder().apply {
this.messageId = msgId
this.messageId = msgId.toString()
}.build()
}.build())
} else if (element.lightApp != null) {
@ -225,9 +227,9 @@ suspend fun List<Elem>.toKritorResponseMessages(contact: Contact): ArrayList<Ele
46 -> {
val buttonExtra = commonElem.elem!!.decodeProtobuf<ButtonExtra>()
kritorMessages.add(
Element.newBuilder().setButton(ButtonElement.newBuilder().apply {
Element.newBuilder().setKeyboard(KeyboardElement.newBuilder().apply {
this.addAllRows(buttonExtra.field1!!.rows!!.map { row ->
ButtonRow.newBuilder().apply {
KeyboardRow.newBuilder().apply {
this.addAllButtons(row.buttons!!.map { button ->
Button.newBuilder().apply {
this.id = button.id
@ -256,7 +258,7 @@ suspend fun List<Elem>.toKritorResponseMessages(contact: Contact): ArrayList<Ele
})
}.build()
})
this.applicationId = buttonExtra.field1?.appid?.toLong() ?: 0L
this.botAppid = buttonExtra.field1?.appid?.toLong() ?: 0L
}.build()).build()
)
}

View File

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

View File

@ -5,7 +5,7 @@ import com.tencent.qqnt.kernel.nativeinterface.Contact
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
import com.tencent.qqnt.msg.api.IMsgService
import io.kritor.message.*
import io.kritor.common.*
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.shamrock.helper.ActionMsgException
@ -135,8 +135,8 @@ private object ReqMsgConvertor {
val elem = Element.newBuilder()
elem.setImage(ImageElement.newBuilder().apply {
this.file = md5
this.url = when (contact.chatType) {
this.fileMd5 = md5
this.fileUrl = when (contact.chatType) {
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(
originalUrl = originalUrl,
md5 = md5,
@ -175,7 +175,7 @@ private object ReqMsgConvertor {
else -> throw UnsupportedOperationException("Not supported chat type: ${contact.chatType}")
}
this.type =
if (image.isFlashPic == true) ImageType.FLASH else if (image.original) ImageType.ORIGIN else ImageType.COMMON
if (image.isFlashPic == true) ImageElement.ImageType.FLASH else if (image.original) ImageElement.ImageType.ORIGIN else ImageElement.ImageType.COMMON
this.subType = image.picSubType
})
@ -191,7 +191,7 @@ private object ReqMsgConvertor {
else ptt.md5HexStr
elem.setVoice(VoiceElement.newBuilder().apply {
this.url = when (contact.chatType) {
this.fileUrl = when (contact.chatType) {
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPttDownUrl("0", ptt.fileUuid)
MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupPttDownUrl(
"0",
@ -201,7 +201,7 @@ private object ReqMsgConvertor {
else -> throw UnsupportedOperationException("Not supported chat type: ${contact.chatType}")
}
this.file = md5
this.fileMd5 = md5
this.magic = ptt.voiceChangeType != MsgConstant.KPTTVOICECHANGETYPENONE
})
@ -219,8 +219,8 @@ private object ReqMsgConvertor {
}
} else video.fileName.split(".")[0].hex2ByteArray()
elem.setVideo(VideoElement.newBuilder().apply {
this.file = md5.toHexString()
this.url = when (contact.chatType) {
this.fileMd5 = md5.toHexString()
this.fileUrl = when (contact.chatType) {
MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid)
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", md5, video.fileUuid)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid)
@ -233,7 +233,11 @@ private object ReqMsgConvertor {
suspend fun convertMarketFace(contact: Contact, element: MsgElement): Result<Element> {
val marketFace = element.marketFaceElement
val elem = Element.newBuilder()
return Result.failure(ActionMsgException)
elem.setMarketFace(MarketFaceElement.newBuilder().apply {
this.id = marketFace.emojiId
})
// TODO
return Result.success(elem.build())
}
suspend fun convertStructJson(contact: Contact, element: MsgElement): Result<Element> {
@ -300,9 +304,9 @@ private object ReqMsgConvertor {
}
if (sourceRecords.isNullOrEmpty()) {
LogCenter.log("无法查询到回复的消息ID: seq = $msgSeq", Level.WARN)
this.messageId = reply.replayMsgId
this.messageId = reply.replayMsgId.toString()
} else {
this.messageId = sourceRecords.first().msgId
this.messageId = sourceRecords.first().msgId.toString()
}
})
return Result.success(elem.build())
@ -362,9 +366,9 @@ private object ReqMsgConvertor {
suspend fun convertInlineKeyboard(contact: Contact, element: MsgElement): Result<Element> {
val inlineKeyboard = element.inlineKeyboardElement
val elem = Element.newBuilder()
elem.setButton(ButtonElement.newBuilder().apply {
elem.setKeyboard(KeyboardElement.newBuilder().apply {
this.addAllRows(inlineKeyboard.rows.map { row ->
ButtonRow.newBuilder().apply {
KeyboardRow.newBuilder().apply {
this.addAllButtons(row.buttons.map { button ->
Button.newBuilder().apply {
this.id = button.id
@ -393,7 +397,7 @@ private object ReqMsgConvertor {
})
}.build()
})
this.applicationId = inlineKeyboard.botAppid
this.botAppid = inlineKeyboard.botAppid
})
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.MsgConstant
import com.tencent.qqnt.msg.api.IMsgService
import io.kritor.message.AtElement
import io.kritor.message.Element
import io.kritor.message.ElementType
import io.kritor.message.ImageElement
import io.kritor.message.ImageType
import io.kritor.common.Element
import io.kritor.common.ImageElement
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.shamrock.helper.Level
@ -68,7 +65,7 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
forEach {
try {
when(it.type!!) {
ElementType.TEXT -> {
Element.ElementType.TEXT -> {
val text = it.text.text
val elem = Elem(
text = TextMsg(text)
@ -76,13 +73,11 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
elems.add(elem)
summary.append(text)
}
ElementType.AT -> {
Element.ElementType.AT -> {
when (contact.chatType) {
MsgConstant.KCHATTYPEGROUP -> {
val qq = when (it.at.accountCase) {
AtElement.AccountCase.UIN -> it.at.uin.toString()
else -> ContactHelper.getUinByUidAsync(it.at.uid)
}
val qq = ContactHelper.getUinByUidAsync(it.at.uid)
val type: Int
val nick = if (it.at.uid == "all" || it.at.uin == 0L) {
type = 1
@ -112,10 +107,7 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
}
MsgConstant.KCHATTYPEC2C -> {
val qq = when (it.at.accountCase) {
AtElement.AccountCase.UIN -> it.at.uin.toString()
else -> ContactHelper.getUinByUidAsync(it.at.uid)
}
val qq = ContactHelper.getUinByUidAsync(it.at.uid)
val display = "@" + (ContactHelper.getProfileCard(qq.toLong()).onSuccess {
it.strNick.ifNullOrEmpty { qq }
}.onFailure {
@ -130,7 +122,7 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
else -> throw UnsupportedOperationException("Unsupported chatType($contact) for AtMsg")
}
}
ElementType.FACE -> {
Element.ElementType.FACE -> {
val faceId = it.face.id
val elem = if (it.face.isBig) {
Elem(
@ -159,12 +151,12 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
elems.add(elem)
summary.append("[表情]")
}
ElementType.BUBBLE_FACE -> throw UnsupportedOperationException("Unsupported ElementType.BUBBLE_FACE")
ElementType.REPLY -> {
Element.ElementType.BUBBLE_FACE -> throw UnsupportedOperationException("Unsupported Element.ElementType.BUBBLE_FACE")
Element.ElementType.REPLY -> {
val msgId = it.reply.messageId
withTimeoutOrNull(3000) {
suspendCancellableCoroutine {
QRoute.api(IMsgService::class.java).getMsgsByMsgId(contact, arrayListOf(msgId)) { _, _, records ->
QRoute.api(IMsgService::class.java).getMsgsByMsgId(contact, arrayListOf(msgId.toLong())) { _, _, records ->
it.resume(records)
}
}
@ -191,9 +183,9 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
}
summary.append("[回复消息]")
}
ElementType.IMAGE -> {
Element.ElementType.IMAGE -> {
val type = it.image.type
val isOriginal = type == ImageType.ORIGIN
val isOriginal = type == ImageElement.ImageType.ORIGIN
val file = when(it.image.dataCase!!) {
ImageElement.DataCase.FILE_NAME -> {
val fileMd5 = it.image.fileName.replace(regex = "[{}\\-]".toRegex(), replacement = "").split(".")[0].lowercase()
@ -205,16 +197,16 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
FileUtils.saveFileToCache(it)
}
}
ImageElement.DataCase.FILE_BASE64 -> {
ImageElement.DataCase.FILE -> {
FileUtils.saveFileToCache(
ByteArrayInputStream(
Base64.decode(it.image.fileBase64, Base64.DEFAULT)
it.image.file.toByteArray()
)
)
}
ImageElement.DataCase.URL -> {
ImageElement.DataCase.FILE_URL -> {
val tmp = FileUtils.getTmpFile()
if(DownloadUtils.download(it.image.url, tmp)) {
if(DownloadUtils.download(it.image.fileUrl, tmp)) {
tmp.inputStream().use {
FileUtils.saveFileToCache(it)
}.also {
@ -222,7 +214,7 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
}
} else {
tmp.delete()
throw LogicException("图片资源下载失败: ${it.image.url}")
throw LogicException("图片资源下载失败: ${it.image.fileUrl}")
}
}
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("[图片]")
}
ElementType.VOICE -> throw UnsupportedOperationException("Unsupported ElementType.VOICE")
ElementType.VIDEO -> throw UnsupportedOperationException("Unsupported ElementType.VIDEO")
ElementType.BASKETBALL -> throw UnsupportedOperationException("Unsupported ElementType.BASKETBALL")
ElementType.DICE -> {
Element.ElementType.VOICE -> throw UnsupportedOperationException("Unsupported Element.ElementType.VOICE")
Element.ElementType.VIDEO -> throw UnsupportedOperationException("Unsupported Element.ElementType.VIDEO")
Element.ElementType.BASKETBALL -> throw UnsupportedOperationException("Unsupported Element.ElementType.BASKETBALL")
Element.ElementType.DICE -> {
val elem = Elem(
commonElem = CommonElem(
serviceType = 37,
@ -375,7 +367,7 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
elems.add(elem)
summary .append( "[骰子]" )
}
ElementType.RPS -> {
Element.ElementType.RPS -> {
val elem = Elem(
commonElem = CommonElem(
serviceType = 37,
@ -395,7 +387,7 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
elems.add(elem)
summary .append( "[包剪锤]" )
}
ElementType.POKE -> {
Element.ElementType.POKE -> {
val elem = Elem(
commonElem = CommonElem(
serviceType = 2,
@ -410,8 +402,8 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
elems.add(elem)
summary .append( "[戳一戳]" )
}
ElementType.MUSIC -> throw UnsupportedOperationException("Unsupported ElementType.MUSIC")
ElementType.WEATHER -> {
Element.ElementType.MUSIC -> throw UnsupportedOperationException("Unsupported Element.ElementType.MUSIC")
Element.ElementType.WEATHER -> {
var code = it.weather.code.toIntOrNull()
if (code == null) {
val city = it.weather.city
@ -438,11 +430,11 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
throw LogicException("无法获取城市天气")
}
}
ElementType.LOCATION -> throw UnsupportedOperationException("Unsupported ElementType.LOCATION")
ElementType.SHARE -> throw UnsupportedOperationException("Unsupported ElementType.SHARE")
ElementType.GIFT -> throw UnsupportedOperationException("Unsupported ElementType.GIFT")
ElementType.MARKET_FACE -> throw UnsupportedOperationException("Unsupported ElementType.MARKET_FACE")
ElementType.FORWARD -> {
Element.ElementType.LOCATION -> throw UnsupportedOperationException("Unsupported Element.ElementType.LOCATION")
Element.ElementType.SHARE -> throw UnsupportedOperationException("Unsupported Element.ElementType.SHARE")
Element.ElementType.GIFT -> throw UnsupportedOperationException("Unsupported Element.ElementType.GIFT")
Element.ElementType.MARKET_FACE -> throw UnsupportedOperationException("Unsupported Element.ElementType.MARKET_FACE")
Element.ElementType.FORWARD -> {
val resId = it.forward.resId
val filename = UUID.randomUUID().toString().uppercase()
var content = it.forward.summary
@ -496,8 +488,8 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
elems.add(elem)
summary.append( "[聊天记录]" )
}
ElementType.CONTACT -> throw UnsupportedOperationException("Unsupported ElementType.CONTACT")
ElementType.JSON -> {
Element.ElementType.CONTACT -> throw UnsupportedOperationException("Unsupported Element.ElementType.CONTACT")
Element.ElementType.JSON -> {
val elem = Elem(
lightApp = LightAppElem(
data = byteArrayOf(1) + DeflateTools.compress(it.json.json.toByteArray())
@ -506,9 +498,9 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
elems.add(elem)
summary .append( "[Json消息]" )
}
ElementType.XML -> throw UnsupportedOperationException("Unsupported ElementType.XML")
ElementType.FILE -> throw UnsupportedOperationException("Unsupported ElementType.FILE")
ElementType.MARKDOWN -> {
Element.ElementType.XML -> throw UnsupportedOperationException("Unsupported Element.ElementType.XML")
Element.ElementType.FILE -> throw UnsupportedOperationException("Unsupported Element.ElementType.FILE")
Element.ElementType.MARKDOWN -> {
val elem = Elem(
commonElem = CommonElem(
serviceType = 45,
@ -519,13 +511,13 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
elems.add(elem)
summary.append("[Markdown消息]")
}
ElementType.BUTTON -> {
Element.ElementType.KEYBOARD -> {
val elem = Elem(
commonElem = CommonElem(
serviceType = 46,
elem = ButtonExtra(
field1 = Object1(
rows = it.button.rowsList.map { row ->
rows = it.keyboard.rowsList.map { row ->
Row(buttons = row.buttonsList.map { button ->
val renderData = button.renderData
val action = button.action
@ -552,7 +544,7 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
)
})
},
appid = it.button.applicationId.toULong()
appid = it.keyboard.botAppid.toULong()
)
).toByteArray(),
businessType = 1
@ -561,8 +553,7 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
elems.add(elem)
summary.append("[Button消息]")
}
ElementType.NODE -> throw UnsupportedOperationException("Unsupported ElementType.NODE")
ElementType.UNRECOGNIZED -> throw UnsupportedOperationException("Unsupported ElementType.UNRECOGNIZED")
Element.ElementType.UNRECOGNIZED -> throw UnsupportedOperationException("Unsupported Element.ElementType.UNRECOGNIZED")
}
} catch (e: Throwable) {
LogCenter.log("转换消息失败(Multi): ${e.stackTraceToString()}", Level.ERROR)