61 Commits

Author SHA1 Message Date
40d2911135 修复资源rkey获取异常 2024-07-16 17:47:39 +08:00
823d9911a0 fix #338 2024-07-16 17:11:48 +08:00
4adbc12a0b fix /get_group_root_files 2024-07-08 09:53:15 +08:00
114fbfdd23 fix /get_group_root_files 2024-07-08 09:44:26 +08:00
0a563d60a1 fix crash in QQ9.0.71 2024-07-08 09:17:56 +08:00
5fb1d0aeb9 fix ping-pong 2024-07-08 09:11:49 +08:00
012ecaa85d fix #335 2024-07-08 08:55:49 +08:00
7ea127a279 update shamrock 2024-07-08 08:15:05 +08:00
02f83a3c48 fix: remove debug log 2024-07-07 15:26:30 +08:00
4016c05882 fix: 9.0.71 compatible issues (part of) 2024-07-07 15:04:30 +08:00
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
cdc664f44a fix build error 2024-03-24 05:33:57 +08:00
ad313f384c fix kritor 2024-03-24 05:19:50 +08:00
b6a510ce05 Update .gitmodules 2024-03-21 19:35:16 +08:00
bed5947909 update kritor 2024-03-21 19:15:58 +08:00
fb6578d243 chore: try to fix ci 2024-03-21 17:50:39 +08:00
d33cace7aa Shamrock: forward messages resources upload 2024-03-21 17:39:51 +08:00
659d4e5da4 commit Readme.md 2024-03-21 16:21:54 +08:00
ac2aee8c0e 関連プロジェクトのヒントを追加する 2024-03-21 16:17:25 +08:00
0faada7b5a Merge pull request #310 from whitechi73/kritor
kritorをmasterブランチに設定する
2024-03-21 16:15:12 +08:00
680317da13 kritorをmasterブランチに設定する
kritorをmasterブランチに設定する
2024-03-21 16:13:45 +08:00
7782feb6ac Merge pull request #303 from tobycroft/master
GetFile的type新增gzip,会将数据流压缩后再b64降低带宽占用
2024-03-18 22:43:18 +08:00
d66358a1f3 Shamrock: 提供开发者服务支持
Signed-off-by: 白池 <whitechi73@outlook.com>
2024-03-18 21:02:05 +08:00
824f280b3a 修改error 提示 2024-03-18 15:40:14 +08:00
6936262d62 GetFile的type新增gzip,会将数据流压缩后再b64降低带宽占用
- 使用gzip压缩
2024-03-18 15:24:24 +08:00
0955267ee5 Merge branch 'whitechi73:master' into master 2024-03-18 13:56:26 +08:00
f3da62fa74 GetFile的type新增gzip,会将数据流压缩后再b64降低带宽占用
- 使用gzip压缩
2024-03-18 13:49:03 +08:00
abbac6315c Merge pull request #301 from tobycroft/master
新增get_file方法(算是补全下功能)
2024-03-18 13:34:51 +08:00
0cf10eabd6 fix: set field file_type not required 2024-03-18 13:34:13 +08:00
8c33267887 fileType加入空匹配,可支持空传 2024-03-18 13:30:18 +08:00
f030104ff2 get_record的ws加入单独的md5字段,方便后续get_file拿文件 2024-03-18 13:23:20 +08:00
ee5fcc3403 Shamrock: 精华消息支持
Signed-off-by: 白池 <whitechi73@outlook.com>
2024-03-18 11:49:38 +08:00
5e819179b4 get_record的ws加入单独的md5字段,方便后续get_file拿文件 2024-03-18 04:08:07 +08:00
ea206faf4f get_record的ws加入单独的md5字段,方便后续get_file拿文件 2024-03-18 04:07:11 +08:00
5adfc544a2 修正file_type参数不正确问题 2024-03-18 03:46:51 +08:00
bdb75841cf AGP更新 2024-03-18 03:23:11 +08:00
a3dc0d06b2 新增get_file方法,主要解决使用反向websocket的时候获取文件麻烦的问题,目前仅支持base64的type返回,未来将支持更多模式,测试后将发布至文档 2024-03-18 03:17:36 +08:00
3664352f23 新增get_file方法,主要解决使用反向websocket的时候获取文件麻烦的问题,目前仅支持base64的type返回,未来将支持更多模式,测试后将发布至文档 2024-03-18 03:05:28 +08:00
2770979fee 新增get_file方法,主要解决使用反向websocket的时候获取文件麻烦的问题,目前仅支持base64的type返回,未来将支持更多模式,测试后将发布至文档 2024-03-18 02:57:44 +08:00
6c9b282c6a Shamrock: 实现消息服务
Signed-off-by: 白池 <whitechi73@outlook.com>
2024-03-16 19:39:45 +08:00
be58c368e9 Merge pull request #295 from tobycroft/master
notice类消息,新增source字段
2024-03-15 00:34:42 +08:00
1d035fa378 Shamrock: fix 群聊和私聊转发分别处理 2024-03-14 18:57:45 +08:00
7d0b60271e Shamrock: fix 群聊转发图片 2024-03-14 18:25:01 +08:00
d38777d06a notice类消息,新增source,解决poke等特殊消息没有办法直接判断消息来源的问题。修改后通过notice类消息的source字段,则可判断需要使用那种struct来接收 2024-03-14 15:05:01 +08:00
93c49953cf Merge pull request #289 from Linwenxuan05/patch-1
添加相关项目
2024-03-11 11:45:56 +08:00
883e949cc1 添加相关项目 2024-03-11 11:41:46 +08:00
109 changed files with 7254 additions and 1240 deletions

View File

@ -19,7 +19,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 submodules: recursive
- name: Setup JDK 17 - name: Setup JDK 17
uses: actions/setup-java@v4 uses: actions/setup-java@v4

2
.gitmodules vendored
View File

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

View File

@ -16,20 +16,37 @@
## 简介 ## 简介
☘ 基于 Lsposed(**Non**-Riru) 实现 OneBot 标准的 QQ 机器人框架,原作者[**fuqiuluo**](https://github.com/fuqiuluo)已脱离开发接下来由白池接手哦本项目为OpenShamrock不会有任何收费行为欢迎大家的加入 ☘ 基于 Lsposed(**Non**-Riru) 实现 Kritor 标准的 QQ 机器人框架!
> 本项目仅提供学习与交流用途请在24小时内删除。 > 本项目仅提供学习与交流用途请在24小时内删除。
> 本项目目的是研究 Xposed 和 LSPosed 框架的使用。 Epic 框架开发相关知识。 > 本项目目的是研究 Xposed 和 LSPosed 框架的使用。 Epic 框架开发相关知识。
> Riru可能导致封禁请减少使用。 > Riru可能导致封禁请减少使用。
> 如有违反法律,请联系删除。 > 如有违反法律,请联系删除。
> 请勿在任何平台宣传,宣扬,转发本项目,请勿恶意修改企业安装包造成相关企业产生损失,如有违背,必将追责到底。 > 请勿在任何平台宣传,宣扬,转发本项目,请勿恶意修改企业安装包造成相关企业产生损失,如有违背,必将追责到底。
> 官方论坛,[点我直达](https://forum.libfekit.so/) >
> 社区地址:[discord](https://discord.gg/MKR2wz863h)
## 兼容|迁移|替代 说明 ## 兼容|迁移|替代 说明
- 一键移植:本项目基于 go-cqhttp 的文档进行开发实现。 - 一键移植:本项目基于 go-cqhttp 的文档进行开发实现。
- 平行部署:可多平台部署,未来将会支持 Docker 部署的教程 - 平行部署:可多平台部署。
- 替代方案:[Lagrange.Core](https://github.com/LagrangeDev/Lagrange.Core)
## 相关项目
<table>
<tr>
<td><a href="https://github.com/LagrangeDev/Lagrange.Core">Lagrange.Core</a></td>
<td>NTQQ 的协议实现</td>
</tr>
<tr>
<td><a href="https://github.com/whitechi73/OpenShamrock">OpenShamrock</a></td>
<td>基于 Xposed 实现 OneBot 标准的机器人框架(👈你在这里</td>
</tr>
<tr>
<td><a href="https://github.com/chrononeko/chronocat">Chronocat</a></td>
<td>基于 Electron 的、模块化的 Satori 框架</td>
</tr>
</table>
## 权限声明 ## 权限声明

View File

@ -1,7 +1,9 @@
package kritor.service package kritor.service
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.FUNCTION) @Target(AnnotationTarget.FUNCTION)
annotation class Grpc( annotation class Grpc(
val serviceName: String, val serviceName: String,
val funcName: String val funcName: String,
) )

View File

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

View File

@ -17,7 +17,7 @@ android {
minSdk = 27 minSdk = 27
targetSdk = 34 targetSdk = 34
versionCode = getVersionCode() versionCode = getVersionCode()
versionName = "1.1.0" + ".r${getGitCommitCount()}." + getVersionName() versionName = "1.1.1" + ".r${getGitCommitCount()}." + getVersionName()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {

View File

@ -118,9 +118,7 @@ private fun APIInfoCard(
text = rpcAddress, text = rpcAddress,
hint = "请输入回调地址", hint = "请输入回调地址",
error = "输入的地址不合法", error = "输入的地址不合法",
checker = { checker = { true },
it.isEmpty() || it.contains(":")
},
confirm = { confirm = {
ShamrockConfig[ctx, RPCAddress] = rpcAddress.value ShamrockConfig[ctx, RPCAddress] = rpcAddress.value
AppRuntime.log("设置回调RPC地址为[${rpcAddress.value}]。") AppRuntime.log("设置回调RPC地址为[${rpcAddress.value}]。")
@ -164,7 +162,7 @@ private fun FunctionCard(
Function( Function(
title = "主动RPC", title = "主动RPC",
desc = "Kritor协议实现RPC", desc = "Kritor协议实现RPC由Shamrock放出rpc服务",
isSwitch = ShamrockConfig[ctx, ActiveRPC] isSwitch = ShamrockConfig[ctx, ActiveRPC]
) { ) {
ShamrockConfig[ctx, ActiveRPC] = it ShamrockConfig[ctx, ActiveRPC] = it
@ -173,7 +171,7 @@ private fun FunctionCard(
Function( Function(
title = "被动RPC", title = "被动RPC",
desc = "Kritor协议实现RPC", desc = "Kritor协议实现RPC由客户端提供反向的rpc服务",
isSwitch = ShamrockConfig[ctx, PassiveRPC] isSwitch = ShamrockConfig[ctx, PassiveRPC]
) { ) {
ShamrockConfig[ctx, PassiveRPC] = it ShamrockConfig[ctx, PassiveRPC] = it

View File

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

View File

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

1
kritor

Submodule kritor deleted from e4aac653e1

42
kritor/.gitignore vendored Normal file
View File

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

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

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

View File

1
kritor/kritor Submodule

Submodule kritor/kritor added at 3dec747a8e

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

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

View File

@ -0,0 +1,83 @@
@file:Suppress("UNCHECKED_CAST")
@file:OptIn(KspExperimental::class)
package moe.fuqiuluo.ksp.impl
import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.getAnnotationsByType
import com.google.devtools.ksp.processing.*
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import kritor.service.Grpc
class GrpcProcessor(
private val codeGenerator: CodeGenerator,
private val logger: KSPLogger
): SymbolProcessor {
override fun process(resolver: Resolver): List<KSAnnotated> {
val symbols = resolver.getSymbolsWithAnnotation(Grpc::class.qualifiedName!!)
val actions = (symbols as Sequence<KSFunctionDeclaration>).toList()
if (actions.isEmpty()) return emptyList()
// 怎么返回nullable的结果
val packageName = "kritor.handlers"
val funcBuilder = FunSpec.builder("handleGrpc")
.addModifiers(KModifier.SUSPEND)
.addParameter("cmd", String::class)
.addParameter("data", ByteArray::class)
.returns(ByteArray::class)
val fileSpec = FileSpec.scriptBuilder("AutoGrpcHandlers", packageName)
logger.warn("Found ${actions.size} grpc-actions")
//logger.error(resolver.getClassDeclarationByName("io.kritor.AuthReq").toString())
//logger.error(resolver.getJavaClassByName("io.kritor.AuthReq").toString())
//logger.error(resolver.getKotlinClassByName("io.kritor.AuthReq").toString())
actions.forEach { action ->
val methodName = action.qualifiedName?.asString()!!
val grpcMethod = action.getAnnotationsByType(Grpc::class).first()
val service = grpcMethod.serviceName
val funcName = grpcMethod.funcName
funcBuilder.addStatement("if (cmd == \"${service}.${funcName}\") {\t")
val reqType = action.parameters[0].type.toString()
val rspType = action.returnType.toString()
funcBuilder.addStatement("val resp: $rspType = $methodName($reqType.parseFrom(data))")
funcBuilder.addStatement("return resp.toByteArray()")
funcBuilder.addStatement("}")
}
funcBuilder.addStatement("return EMPTY_BYTE_ARRAY")
fileSpec
.addStatement("import io.kritor.authentication.*")
.addStatement("import io.kritor.core.*")
.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.*")
.addFunction(funcBuilder.build())
.addImport("moe.fuqiuluo.symbols", "EMPTY_BYTE_ARRAY")
runCatching {
codeGenerator.createNewFile(
dependencies = Dependencies(aggregating = false),
packageName = packageName,
fileName = fileSpec.name
).use { outputStream ->
outputStream.writer().use {
fileSpec.build().writeTo(it)
}
}
}
return emptyList()
}
}

View File

@ -0,0 +1,17 @@
package moe.fuqiuluo.ksp.providers
import com.google.auto.service.AutoService
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider
import moe.fuqiuluo.ksp.impl.GrpcProcessor
@AutoService(SymbolProcessorProvider::class)
class GrpcProvider: SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
return GrpcProcessor(
environment.codeGenerator,
environment.logger
)
}
}

View File

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

View File

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

View File

@ -8,7 +8,9 @@ import moe.fuqiuluo.symbols.Protobuf
data class TrpcOidb( data class TrpcOidb(
@ProtoNumber(1) val cmd: Int = Int.MIN_VALUE, @ProtoNumber(1) val cmd: Int = Int.MIN_VALUE,
@ProtoNumber(2) val service: Int = Int.MIN_VALUE, @ProtoNumber(2) val service: Int = Int.MIN_VALUE,
@ProtoNumber(4) val buffer: ByteArray, @ProtoNumber(3) val result: UInt? = null,
@ProtoNumber(4) val buffer: ByteArray? = null,
@ProtoNumber(5) val msg: String? = null,
//@ProtoNumber(11) val traceParams: Map<String, String> = mapOf(), //@ProtoNumber(11) val traceParams: Map<String, String> = mapOf(),
@ProtoNumber(12) val flag: Int = Int.MIN_VALUE, @ProtoNumber(12) val flag: Int = Int.MIN_VALUE,
): Protobuf<TrpcOidb> ): Protobuf<TrpcOidb>

View File

@ -94,7 +94,8 @@ data class DeleteReq(
@Serializable @Serializable
data class DownloadRkeyReq( data class DownloadRkeyReq(
@ProtoNumber(1) val types: List<Int> @ProtoNumber(1) val types: List<Int>,
@ProtoNumber(2) val downloadType: Int
) )
@Serializable @Serializable

View File

@ -52,11 +52,11 @@ data class DownloadRkeyRsp(
@Serializable @Serializable
data class RKeyInfo( data class RKeyInfo(
@ProtoNumber(1) val rkey: String?, @ProtoNumber(1) val rkey: String,
@ProtoNumber(2) val rkeyTtlSec: ULong?, @ProtoNumber(2) val rkeyTtlSec: ULong?,
@ProtoNumber(3) val storeId: UInt = 0u, @ProtoNumber(3) val storeId: UInt = 0u,
@ProtoNumber(4) val rkeyCreateTime: UInt?, @ProtoNumber(4) val rkeyCreateTime: UInt?,
@ProtoNumber(4) val type: UInt?, @ProtoNumber(4) val type: UInt,
) )
@Serializable @Serializable

View File

@ -1,7 +1,9 @@
package com.tencent.qphone.base.util; package com.tencent.qphone.base.util;
public abstract class CodecWarpper { public abstract class CodecWarpper {
public abstract void onResponse(int i2, Object obj, int i3); // public abstract void onResponse(int i2, Object obj, int i3);
public abstract void onInvalidData(int i2, int i3, String str);
public abstract void onResponse(int i2, Object obj, int i3, byte[] bArr); public abstract void onResponse(int i2, Object obj, int i3, byte[] bArr);

View File

@ -1,7 +1,7 @@
package com.tencent.qqnt.aio.api; package com.tencent.qqnt.aio.api;
import com.tencent.mobileqq.qroute.QRouteApi; import com.tencent.mobileqq.qroute.QRouteApi;
import com.tencent.qqnt.kernel.nativeinterface.Contact; import com.tencent.qqnt.kernelpublic.nativeinterface.Contact;
import com.tencent.qqnt.kernel.nativeinterface.IOperateCallback; import com.tencent.qqnt.kernel.nativeinterface.IOperateCallback;
public interface IAIOFileTransfer extends QRouteApi { public interface IAIOFileTransfer extends QRouteApi {

View File

@ -0,0 +1,4 @@
package com.tencent.qqnt.kernel.api.impl;
public class GroupService {
}

View File

@ -1,5 +1,7 @@
package com.tencent.qqnt.kernel.nativeinterface; package com.tencent.qqnt.kernel.nativeinterface;
import com.tencent.qqnt.kernelpublic.nativeinterface.Contact;
import java.util.ArrayList; import java.util.ArrayList;

View File

@ -0,0 +1,4 @@
package com.tencent.qqnt.kernel.nativeinterface;
public class GProGuildTopFeedMsg {
}

View File

@ -1,5 +1,7 @@
package com.tencent.qqnt.kernel.nativeinterface; package com.tencent.qqnt.kernel.nativeinterface;
import com.tencent.qqnt.kernelpublic.nativeinterface.Contact;
import java.util.ArrayList; import java.util.ArrayList;

View File

@ -0,0 +1,36 @@
package com.tencent.qqnt.kernel.nativeinterface;
import java.util.ArrayList;
public class GroupMemberCommonListResult {
public long groupCode;
public int identifyFlag;
public long startUin;
public ArrayList<MemberCommonInfo> memberList = new ArrayList<>();
public String strErrorInfo = "";
public long getGroupCode() {
return this.groupCode;
}
public int getIdentifyFlag() {
return this.identifyFlag;
}
public ArrayList<MemberCommonInfo> getMemberList() {
return this.memberList;
}
public long getStartUin() {
return this.startUin;
}
public String getStrErrorInfo() {
return this.strErrorInfo;
}
public String toString() {
return "GroupMemberCommonListResult{groupCode=" + this.groupCode + ",startUin=" + this.startUin + ",identifyFlag=" + this.identifyFlag + ",memberList=" + this.memberList + ",strErrorInfo=" + this.strErrorInfo + ",}";
}
}

View File

@ -0,0 +1,101 @@
package com.tencent.qqnt.kernel.nativeinterface;
import java.util.ArrayList;
public class GroupMemberCommonReq {
public long groupCode;
public int sourceType;
public String startUin = "";
public String identifyFlag = "";
public ArrayList<Long> uinList = new ArrayList<>();
public MemberCommonInfoFilter memberCommonFilter = new MemberCommonInfoFilter();
public String memberNum = "";
public String filterMethod = "";
public String onlineFlag = "";
public String realSpecialTitleFlag = "";
public String getFilterMethod() {
return this.filterMethod;
}
public long getGroupCode() {
return this.groupCode;
}
public String getIdentifyFlag() {
return this.identifyFlag;
}
public MemberCommonInfoFilter getMemberCommonFilter() {
return this.memberCommonFilter;
}
public String getMemberNum() {
return this.memberNum;
}
public String getOnlineFlag() {
return this.onlineFlag;
}
public String getRealSpecialTitleFlag() {
return this.realSpecialTitleFlag;
}
public int getSourceType() {
return this.sourceType;
}
public String getStartUin() {
return this.startUin;
}
public ArrayList<Long> getUinList() {
return this.uinList;
}
public void setFilterMethod(String str) {
this.filterMethod = str;
}
public void setGroupCode(long j2) {
this.groupCode = j2;
}
public void setIdentifyFlag(String str) {
this.identifyFlag = str;
}
public void setMemberCommonFilter(MemberCommonInfoFilter memberCommonInfoFilter) {
this.memberCommonFilter = memberCommonInfoFilter;
}
public void setMemberNum(String str) {
this.memberNum = str;
}
public void setOnlineFlag(String str) {
this.onlineFlag = str;
}
public void setRealSpecialTitleFlag(String str) {
this.realSpecialTitleFlag = str;
}
public void setSourceType(int i2) {
this.sourceType = i2;
}
public void setStartUin(String str) {
this.startUin = str;
}
public void setUinList(ArrayList<Long> arrayList) {
this.uinList = arrayList;
}
public String toString() {
return "GroupMemberCommonReq{groupCode=" + this.groupCode + ",startUin=" + this.startUin + ",identifyFlag=" + this.identifyFlag + ",uinList=" + this.uinList + ",memberCommonFilter=" + this.memberCommonFilter + ",memberNum=" + this.memberNum + ",filterMethod=" + this.filterMethod + ",onlineFlag=" + this.onlineFlag + ",realSpecialTitleFlag=" + this.realSpecialTitleFlag + ",sourceType=" + this.sourceType + ",}";
}
}

View File

@ -0,0 +1,81 @@
package com.tencent.qqnt.kernel.nativeinterface;
import java.util.ArrayList;
public class GroupMemberExtListResult {
public long dataTime;
public long endUin;
public long groupCode;
public int levelNameSeq;
public int memberInfoSeq;
public int sysShowFlag;
public int timeToUpdate;
public int userShowFlag;
public int userShowFlagNew;
public ArrayList<MemberExtInfo> memberLevelInfo = new ArrayList<>();
public ArrayList<MemberLevelName> msgLevelName = new ArrayList<>();
public String strOwnerName = "";
public String strAdminName = "";
public ArrayList<MemberLevelName> msgLevelNameNew = new ArrayList<>();
public long getDataTime() {
return this.dataTime;
}
public long getEndUin() {
return this.endUin;
}
public long getGroupCode() {
return this.groupCode;
}
public int getLevelNameSeq() {
return this.levelNameSeq;
}
public int getMemberInfoSeq() {
return this.memberInfoSeq;
}
public ArrayList<MemberExtInfo> getMemberLevelInfo() {
return this.memberLevelInfo;
}
public ArrayList<MemberLevelName> getMsgLevelName() {
return this.msgLevelName;
}
public ArrayList<MemberLevelName> getMsgLevelNameNew() {
return this.msgLevelNameNew;
}
public String getStrAdminName() {
return this.strAdminName;
}
public String getStrOwnerName() {
return this.strOwnerName;
}
public int getSysShowFlag() {
return this.sysShowFlag;
}
public int getTimeToUpdate() {
return this.timeToUpdate;
}
public int getUserShowFlag() {
return this.userShowFlag;
}
public int getUserShowFlagNew() {
return this.userShowFlagNew;
}
public String toString() {
return "GroupMemberExtListResult{groupCode=" + this.groupCode + ",memberLevelInfo=" + this.memberLevelInfo + ",msgLevelName=" + this.msgLevelName + ",endUin=" + this.endUin + ",dataTime=" + this.dataTime + ",userShowFlag=" + this.userShowFlag + ",sysShowFlag=" + this.sysShowFlag + ",timeToUpdate=" + this.timeToUpdate + ",strOwnerName=" + this.strOwnerName + ",strAdminName=" + this.strAdminName + ",levelNameSeq=" + this.levelNameSeq + ",userShowFlagNew=" + this.userShowFlagNew + ",msgLevelNameNew=" + this.msgLevelNameNew + ",memberInfoSeq=" + this.memberInfoSeq + ",}";
}
}

View File

@ -0,0 +1,101 @@
package com.tencent.qqnt.kernel.nativeinterface;
import java.util.ArrayList;
public class GroupMemberExtReq {
public long groupCode;
public int sourceType;
public String beginUin = "";
public String dataTime = "";
public ArrayList<Long> uinList = new ArrayList<>();
public MemberExtInfoFilter memberExtFilter = new MemberExtInfoFilter();
public String seq = "";
public String uinNum = "";
public String groupType = "";
public String richCardNameVer = "";
public String getBeginUin() {
return this.beginUin;
}
public String getDataTime() {
return this.dataTime;
}
public long getGroupCode() {
return this.groupCode;
}
public String getGroupType() {
return this.groupType;
}
public MemberExtInfoFilter getMemberExtFilter() {
return this.memberExtFilter;
}
public String getRichCardNameVer() {
return this.richCardNameVer;
}
public String getSeq() {
return this.seq;
}
public int getSourceType() {
return this.sourceType;
}
public ArrayList<Long> getUinList() {
return this.uinList;
}
public String getUinNum() {
return this.uinNum;
}
public void setBeginUin(String str) {
this.beginUin = str;
}
public void setDataTime(String str) {
this.dataTime = str;
}
public void setGroupCode(long j2) {
this.groupCode = j2;
}
public void setGroupType(String str) {
this.groupType = str;
}
public void setMemberExtFilter(MemberExtInfoFilter memberExtInfoFilter) {
this.memberExtFilter = memberExtInfoFilter;
}
public void setRichCardNameVer(String str) {
this.richCardNameVer = str;
}
public void setSeq(String str) {
this.seq = str;
}
public void setSourceType(int i2) {
this.sourceType = i2;
}
public void setUinList(ArrayList<Long> arrayList) {
this.uinList = arrayList;
}
public void setUinNum(String str) {
this.uinNum = str;
}
public String toString() {
return "GroupMemberExtReq{groupCode=" + this.groupCode + ",beginUin=" + this.beginUin + ",dataTime=" + this.dataTime + ",uinList=" + this.uinList + ",memberExtFilter=" + this.memberExtFilter + ",seq=" + this.seq + ",uinNum=" + this.uinNum + ",groupType=" + this.groupType + ",richCardNameVer=" + this.richCardNameVer + ",sourceType=" + this.sourceType + ",}";
}
}

View File

@ -0,0 +1,32 @@
package com.tencent.qqnt.kernel.nativeinterface;
import java.util.ArrayList;
import java.util.HashMap;
public class GroupMemberListResult {
public boolean finish;
public boolean hasRobot;
public ArrayList<GroupMemberInfoListId> ids = new ArrayList<>();
public HashMap<String, MemberInfo> infos = new HashMap<>();
public boolean getFinish() {
return this.finish;
}
public boolean getHasRobot() {
return this.hasRobot;
}
public ArrayList<GroupMemberInfoListId> getIds() {
return this.ids;
}
public HashMap<String, MemberInfo> getInfos() {
return this.infos;
}
public String toString() {
return "GroupMemberListResult{ids=" + this.ids + ",infos=" + this.infos + ",finish=" + this.finish + ",hasRobot=" + this.hasRobot + ",}";
}
}

View File

@ -0,0 +1,6 @@
package com.tencent.qqnt.kernel.nativeinterface;
public interface IGroupMemberCommonCallback {
void onResult(int i2, String str, GroupMemberCommonListResult groupMemberCommonListResult);
}

View File

@ -0,0 +1,5 @@
package com.tencent.qqnt.kernel.nativeinterface;
public interface IGroupMemberExtCallback {
void onResult(int i2, String str, GroupMemberExtListResult groupMemberExtListResult);
}

View File

@ -0,0 +1,6 @@
package com.tencent.qqnt.kernel.nativeinterface;
public interface IGroupMemberListCallback {
void onResult(int result, String str, GroupMemberListResult groupMemberListResult);
}

View File

@ -1,7 +1,25 @@
package com.tencent.qqnt.kernel.nativeinterface; package com.tencent.qqnt.kernel.nativeinterface;
import java.util.ArrayList;
public interface IKernelGroupService { public interface IKernelGroupService {
void getTransferableMemberInfo(long uin, IGetTransferableMemberCallback cb); void getTransferableMemberInfo(long uin, IGetTransferableMemberCallback cb);
long addKernelGroupListener(IKernelGroupListener ln); long addKernelGroupListener(IKernelGroupListener ln);
void getAllMemberList(long groupCode, boolean refresh, IGroupMemberListCallback iGroupMemberListCallback);
void getMemberCommonInfo(GroupMemberCommonReq groupMemberCommonReq, IGroupMemberCommonCallback iGroupMemberCommonCallback);
void getMemberExtInfo(GroupMemberExtReq groupMemberExtReq, IGroupMemberExtCallback iGroupMemberExtCallback);
void getMemberInfo(long j2, ArrayList<String> arrayList, boolean z, IOperateCallback iOperateCallback);
void getMemberInfoForMqq(long groupCode, ArrayList<String> uids, boolean z, IGroupMemberListCallback iGroupMemberListCallback);
void getNextMemberList(String sceneId, GroupMemberInfoListId groupMemberInfoListId, int i2, IGroupMemberListCallback iGroupMemberListCallback);
void getPrevMemberList(String sceneId, GroupMemberInfoListId groupMemberInfoListId, int i2, IGroupMemberListCallback iGroupMemberListCallback);
} }

View File

@ -1,5 +1,7 @@
package com.tencent.qqnt.kernel.nativeinterface; package com.tencent.qqnt.kernel.nativeinterface;
import com.tencent.qqnt.kernelpublic.nativeinterface.Contact;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@ -16,7 +18,7 @@ public interface IKernelMsgListener {
void onCustomWithdrawConfigUpdate(CustomWithdrawConfig customWithdrawConfig); void onCustomWithdrawConfigUpdate(CustomWithdrawConfig customWithdrawConfig);
void onDraftUpdate(Contact contact, ArrayList<MsgElement> arrayList, long j2); void onDraftUpdate(Contact contact, ArrayList<MsgElement> arrayList, long j);
void onEmojiDownloadComplete(EmojiNotifyInfo emojiNotifyInfo); void onEmojiDownloadComplete(EmojiNotifyInfo emojiNotifyInfo);
@ -30,7 +32,7 @@ public interface IKernelMsgListener {
void onFirstViewGroupGuildMapping(ArrayList<FirstViewGroupGuildInfo> arrayList); void onFirstViewGroupGuildMapping(ArrayList<FirstViewGroupGuildInfo> arrayList);
void onGrabPasswordRedBag(int i2, String str, int i3, RecvdOrder recvdOrder, MsgRecord msgRecord); void onGrabPasswordRedBag(int i, String str, int i2, RecvdOrder recvdOrder, MsgRecord msgRecord);
void onGroupFileInfoAdd(GroupItem groupItem); void onGroupFileInfoAdd(GroupItem groupItem);
@ -48,6 +50,8 @@ public interface IKernelMsgListener {
void onGuildNotificationAbstractUpdate(GuildNotificationAbstractInfo guildNotificationAbstractInfo); void onGuildNotificationAbstractUpdate(GuildNotificationAbstractInfo guildNotificationAbstractInfo);
void onGuildTopFeedUpdate(GProGuildTopFeedMsg gProGuildTopFeedMsg);
void onHitCsRelatedEmojiResult(DownloadRelateEmojiResultInfo downloadRelateEmojiResultInfo); void onHitCsRelatedEmojiResult(DownloadRelateEmojiResultInfo downloadRelateEmojiResultInfo);
void onHitEmojiKeywordResult(HitRelatedEmojiWordsResult hitRelatedEmojiWordsResult); void onHitEmojiKeywordResult(HitRelatedEmojiWordsResult hitRelatedEmojiWordsResult);
@ -62,7 +66,7 @@ public interface IKernelMsgListener {
void onLineDev(ArrayList<DevInfo> arrayList); void onLineDev(ArrayList<DevInfo> arrayList);
void onLogLevelChanged(long j2); void onLogLevelChanged(long j);
void onMsgAbstractUpdate(ArrayList<MsgAbstract> arrayList); void onMsgAbstractUpdate(ArrayList<MsgAbstract> arrayList);
@ -76,14 +80,16 @@ public interface IKernelMsgListener {
void onMsgInfoListUpdate(ArrayList<MsgRecord> arrayList); void onMsgInfoListUpdate(ArrayList<MsgRecord> arrayList);
void onMsgQRCodeStatusChanged(int i2); void onMsgQRCodeStatusChanged(int i);
void onMsgRecall(int i2, String str, long j2); void onMsgRecall(int i, String str, long j);
void onMsgSecurityNotify(MsgRecord msgRecord); void onMsgSecurityNotify(MsgRecord msgRecord);
void onMsgSettingUpdate(MsgSetting msgSetting); void onMsgSettingUpdate(MsgSetting msgSetting);
void onMsgWithRichLinkInfoUpdate(ArrayList<MsgRecord> arrayList);
void onNtFirstViewMsgSyncEnd(); void onNtFirstViewMsgSyncEnd();
void onNtMsgSyncEnd(); void onNtMsgSyncEnd();
@ -92,11 +98,11 @@ public interface IKernelMsgListener {
void onReadFeedEventUpdate(FirstViewDirectMsgNotifyInfo firstViewDirectMsgNotifyInfo); void onReadFeedEventUpdate(FirstViewDirectMsgNotifyInfo firstViewDirectMsgNotifyInfo);
void onRecvGroupGuildFlag(int i2); void onRecvGroupGuildFlag(int i);
void onRecvMsg(ArrayList<MsgRecord> arrayList); void onRecvMsg(ArrayList<MsgRecord> arrayList);
void onRecvMsgSvrRspTransInfo(long j2, Contact contact, int i2, int i3, String str, byte[] bArr); void onRecvMsgSvrRspTransInfo(long j, Contact contact, int i, int i2, String str, byte[] bArr);
void onRecvOnlineFileMsg(ArrayList<MsgRecord> arrayList); void onRecvOnlineFileMsg(ArrayList<MsgRecord> arrayList);
@ -104,7 +110,9 @@ public interface IKernelMsgListener {
void onRecvSysMsg(ArrayList<Byte> arrayList); void onRecvSysMsg(ArrayList<Byte> arrayList);
void onRecvUDCFlag(int i2); void onRecvUDCFlag(int i);
void onRedTouchChanged();
void onRichMediaDownloadComplete(FileTransNotifyInfo fileTransNotifyInfo); void onRichMediaDownloadComplete(FileTransNotifyInfo fileTransNotifyInfo);
@ -114,9 +122,9 @@ public interface IKernelMsgListener {
void onSearchGroupFileInfoUpdate(SearchGroupFileResult searchGroupFileResult); void onSearchGroupFileInfoUpdate(SearchGroupFileResult searchGroupFileResult);
void onSendMsgError(long j2, Contact contact, int i2, String str); void onSendMsgError(long j, Contact contact, int i, String str);
void onSysMsgNotification(int i2, long j2, long j3, ArrayList<Byte> arrayList); void onSysMsgNotification(int i, long j, long j2, boolean z, ArrayList<Byte> arrayList);
void onTempChatInfoUpdate(TempChatInfo tempChatInfo); void onTempChatInfoUpdate(TempChatInfo tempChatInfo);
@ -128,9 +136,11 @@ public interface IKernelMsgListener {
void onUserOnlineStatusChanged(boolean z); void onUserOnlineStatusChanged(boolean z);
void onUserSecQualityChanged(QueryUserSecQualityRsp queryUserSecQualityRsp);
void onUserTabStatusChanged(ArrayList<TabStatusInfo> arrayList); void onUserTabStatusChanged(ArrayList<TabStatusInfo> arrayList);
void onlineStatusBigIconDownloadPush(int i2, long j2, String str); void onlineStatusBigIconDownloadPush(int i, long j, String str);
void onlineStatusSmallIconDownloadPush(int i2, long j2, String str); void onlineStatusSmallIconDownloadPush(int i, long j, String str);
} }

View File

@ -1,5 +1,7 @@
package com.tencent.qqnt.kernel.nativeinterface; package com.tencent.qqnt.kernel.nativeinterface;
import com.tencent.qqnt.kernelpublic.nativeinterface.Contact;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;

View File

@ -1,5 +1,7 @@
package com.tencent.qqnt.kernel.nativeinterface; package com.tencent.qqnt.kernel.nativeinterface;
import com.tencent.qqnt.kernelpublic.nativeinterface.Contact;
import java.util.ArrayList; import java.util.ArrayList;
public interface IKernelRichMediaService { public interface IKernelRichMediaService {

View File

@ -0,0 +1,63 @@
package com.tencent.qqnt.kernel.nativeinterface;
public class MemberCommonInfo {
public long memberUin;
public int privilege;
public int shutUpTime;
public int uinFlag;
public int uinFlagExt;
public int uinMobileFlag;
public long getMemberUin() {
return this.memberUin;
}
public int getPrivilege() {
return this.privilege;
}
public int getShutUpTime() {
return this.shutUpTime;
}
public int getUinFlag() {
return this.uinFlag;
}
public int getUinFlagExt() {
return this.uinFlagExt;
}
public int getUinMobileFlag() {
return this.uinMobileFlag;
}
public void setMemberUin(long j2) {
this.memberUin = j2;
}
public void setPrivilege(int i2) {
this.privilege = i2;
}
public void setShutUpTime(int i2) {
this.shutUpTime = i2;
}
public void setUinFlag(int i2) {
this.uinFlag = i2;
}
public void setUinFlagExt(int i2) {
this.uinFlagExt = i2;
}
public void setUinMobileFlag(int i2) {
this.uinMobileFlag = i2;
}
public String toString() {
return "MemberCommonInfo{memberUin=" + this.memberUin + ",uinFlag=" + this.uinFlag + ",uinFlagExt=" + this.uinFlagExt + ",uinMobileFlag=" + this.uinMobileFlag + ",shutUpTime=" + this.shutUpTime + ",privilege=" + this.privilege + ",}";
}
}

View File

@ -0,0 +1,63 @@
package com.tencent.qqnt.kernel.nativeinterface;
public class MemberCommonInfoFilter {
public int memberUin;
public int privilege;
public int shutUpTime;
public int uinFlag;
public int uinFlagExt;
public int uinMobileFlag;
public int getMemberUin() {
return this.memberUin;
}
public int getPrivilege() {
return this.privilege;
}
public int getShutUpTime() {
return this.shutUpTime;
}
public int getUinFlag() {
return this.uinFlag;
}
public int getUinFlagExt() {
return this.uinFlagExt;
}
public int getUinMobileFlag() {
return this.uinMobileFlag;
}
public void setMemberUin(int i2) {
this.memberUin = i2;
}
public void setPrivilege(int i2) {
this.privilege = i2;
}
public void setShutUpTime(int i2) {
this.shutUpTime = i2;
}
public void setUinFlag(int i2) {
this.uinFlag = i2;
}
public void setUinFlagExt(int i2) {
this.uinFlagExt = i2;
}
public void setUinMobileFlag(int i2) {
this.uinMobileFlag = i2;
}
public String toString() {
return "MemberCommonInfoFilter{memberUin=" + this.memberUin + ",uinFlag=" + this.uinFlag + ",uinFlagExt=" + this.uinFlagExt + ",uinMobileFlag=" + this.uinMobileFlag + ",shutUpTime=" + this.shutUpTime + ",privilege=" + this.privilege + ",}";
}
}

View File

@ -0,0 +1,110 @@
package com.tencent.qqnt.kernel.nativeinterface;
import java.util.ArrayList;
public class MemberExtInfo {
public int activeDay;
public int cmdUinFlagExt3Grocery;
public int level;
public int point;
public long specialTitleExpireTime;
public long uin;
public String strName = "";
public String nickName = "";
public String specialTitle = "";
public byte[] msgNeedField = new byte[0];
public ArrayList<MemberIcon> memberIconList = new ArrayList<>();
public int getActiveDay() {
return this.activeDay;
}
public int getCmdUinFlagExt3Grocery() {
return this.cmdUinFlagExt3Grocery;
}
public int getLevel() {
return this.level;
}
public ArrayList<MemberIcon> getMemberIconList() {
return this.memberIconList;
}
public byte[] getMsgNeedField() {
return this.msgNeedField;
}
public String getNickName() {
return this.nickName;
}
public int getPoint() {
return this.point;
}
public String getSpecialTitle() {
return this.specialTitle;
}
public long getSpecialTitleExpireTime() {
return this.specialTitleExpireTime;
}
public String getStrName() {
return this.strName;
}
public long getUin() {
return this.uin;
}
public void setActiveDay(int i2) {
this.activeDay = i2;
}
public void setCmdUinFlagExt3Grocery(int i2) {
this.cmdUinFlagExt3Grocery = i2;
}
public void setLevel(int i2) {
this.level = i2;
}
public void setMemberIconList(ArrayList<MemberIcon> arrayList) {
this.memberIconList = arrayList;
}
public void setMsgNeedField(byte[] bArr) {
this.msgNeedField = bArr;
}
public void setNickName(String str) {
this.nickName = str;
}
public void setPoint(int i2) {
this.point = i2;
}
public void setSpecialTitle(String str) {
this.specialTitle = str;
}
public void setSpecialTitleExpireTime(long j2) {
this.specialTitleExpireTime = j2;
}
public void setStrName(String str) {
this.strName = str;
}
public void setUin(long j2) {
this.uin = j2;
}
public String toString() {
return "MemberExtInfo{uin=" + this.uin + ",point=" + this.point + ",activeDay=" + this.activeDay + ",level=" + this.level + ",strName=" + this.strName + ",nickName=" + this.nickName + ",specialTitle=" + this.specialTitle + ",specialTitleExpireTime=" + this.specialTitleExpireTime + ",msgNeedField=" + this.msgNeedField + ",cmdUinFlagExt3Grocery=" + this.cmdUinFlagExt3Grocery + ",memberIconList=" + this.memberIconList + ",}";
}
}

View File

@ -0,0 +1,171 @@
package com.tencent.qqnt.kernel.nativeinterface;
public class MemberExtInfoFilter {
public int cmdUinFlagExt3Grocery;
public int dataTime;
public int levelName;
public int levelNameNew;
public int memberIcon;
public int memberInfoSeq;
public int memberLevelInfoActiveDay;
public int memberLevelInfoLevel;
public int memberLevelInfoName;
public int memberLevelInfoPoint;
public int memberLevelInfoUin;
public int msgNeedField;
public int nickName;
public int specialTitle;
public int sysShowFlag;
public int timeToUpdate;
public int userShowFlag;
public int userShowFlagNew;
public int getCmdUinFlagExt3Grocery() {
return this.cmdUinFlagExt3Grocery;
}
public int getDataTime() {
return this.dataTime;
}
public int getLevelName() {
return this.levelName;
}
public int getLevelNameNew() {
return this.levelNameNew;
}
public int getMemberIcon() {
return this.memberIcon;
}
public int getMemberInfoSeq() {
return this.memberInfoSeq;
}
public int getMemberLevelInfoActiveDay() {
return this.memberLevelInfoActiveDay;
}
public int getMemberLevelInfoLevel() {
return this.memberLevelInfoLevel;
}
public int getMemberLevelInfoName() {
return this.memberLevelInfoName;
}
public int getMemberLevelInfoPoint() {
return this.memberLevelInfoPoint;
}
public int getMemberLevelInfoUin() {
return this.memberLevelInfoUin;
}
public int getMsgNeedField() {
return this.msgNeedField;
}
public int getNickName() {
return this.nickName;
}
public int getSpecialTitle() {
return this.specialTitle;
}
public int getSysShowFlag() {
return this.sysShowFlag;
}
public int getTimeToUpdate() {
return this.timeToUpdate;
}
public int getUserShowFlag() {
return this.userShowFlag;
}
public int getUserShowFlagNew() {
return this.userShowFlagNew;
}
public void setCmdUinFlagExt3Grocery(int i2) {
this.cmdUinFlagExt3Grocery = i2;
}
public void setDataTime(int i2) {
this.dataTime = i2;
}
public void setLevelName(int i2) {
this.levelName = i2;
}
public void setLevelNameNew(int i2) {
this.levelNameNew = i2;
}
public void setMemberIcon(int i2) {
this.memberIcon = i2;
}
public void setMemberInfoSeq(int i2) {
this.memberInfoSeq = i2;
}
public void setMemberLevelInfoActiveDay(int i2) {
this.memberLevelInfoActiveDay = i2;
}
public void setMemberLevelInfoLevel(int i2) {
this.memberLevelInfoLevel = i2;
}
public void setMemberLevelInfoName(int i2) {
this.memberLevelInfoName = i2;
}
public void setMemberLevelInfoPoint(int i2) {
this.memberLevelInfoPoint = i2;
}
public void setMemberLevelInfoUin(int i2) {
this.memberLevelInfoUin = i2;
}
public void setMsgNeedField(int i2) {
this.msgNeedField = i2;
}
public void setNickName(int i2) {
this.nickName = i2;
}
public void setSpecialTitle(int i2) {
this.specialTitle = i2;
}
public void setSysShowFlag(int i2) {
this.sysShowFlag = i2;
}
public void setTimeToUpdate(int i2) {
this.timeToUpdate = i2;
}
public void setUserShowFlag(int i2) {
this.userShowFlag = i2;
}
public void setUserShowFlagNew(int i2) {
this.userShowFlagNew = i2;
}
public String toString() {
return "MemberExtInfoFilter{memberLevelInfoUin=" + this.memberLevelInfoUin + ",memberLevelInfoPoint=" + this.memberLevelInfoPoint + ",memberLevelInfoActiveDay=" + this.memberLevelInfoActiveDay + ",memberLevelInfoLevel=" + this.memberLevelInfoLevel + ",memberLevelInfoName=" + this.memberLevelInfoName + ",levelName=" + this.levelName + ",dataTime=" + this.dataTime + ",userShowFlag=" + this.userShowFlag + ",sysShowFlag=" + this.sysShowFlag + ",timeToUpdate=" + this.timeToUpdate + ",nickName=" + this.nickName + ",specialTitle=" + this.specialTitle + ",levelNameNew=" + this.levelNameNew + ",userShowFlagNew=" + this.userShowFlagNew + ",msgNeedField=" + this.msgNeedField + ",cmdUinFlagExt3Grocery=" + this.cmdUinFlagExt3Grocery + ",memberIcon=" + this.memberIcon + ",memberInfoSeq=" + this.memberInfoSeq + ",}";
}
}

View File

@ -0,0 +1,29 @@
package com.tencent.qqnt.kernel.nativeinterface;
public class MemberIcon {
public int bizId;
public byte[] exInfo = new byte[0];
public long expireTime;
public int resId;
public int getBizId() {
return this.bizId;
}
public byte[] getExInfo() {
return this.exInfo;
}
public long getExpireTime() {
return this.expireTime;
}
public int getResId() {
return this.resId;
}
public String toString() {
return "MemberIcon{resId=" + this.resId + ",expireTime=" + this.expireTime + ",bizId=" + this.bizId + ",exInfo=" + this.exInfo + ",}";
}
}

View File

@ -1,51 +1,146 @@
package com.tencent.qqnt.kernel.nativeinterface; package com.tencent.qqnt.kernel.nativeinterface;
public final class MemberInfo implements IKernelModel { import com.tencent.qqnt.kernelpublic.nativeinterface.MemberRole;
String avatarPath;
String cardName;
int cardType;
boolean isDelete;
boolean isSpecialConcerned;
String nick;
String qid;
String remark;
MemberRole role;
long serialVersionUID;
int shutUpTime;
String uid;
long uin;
public MemberInfo() { public final class MemberInfo implements IKernelModel {
this.serialVersionUID = 1L; public int bigClubFlag;
this.uid = ""; public int bigClubLevel;
this.qid = ""; public int cardNameId;
this.nick = ""; public int cardType;
this.remark = ""; public int creditLevel;
this.cardName = ""; public int globalGroupLevel;
this.role = MemberRole.values()[0]; public int globalGroupPoint;
this.avatarPath = ""; public boolean isDelete;
public boolean isRobot;
public boolean isSpecialConcerned;
public boolean isSpecialShielded;
public int joinTime;
public int lastSpeakTime;
public int memberFlag;
public int memberFlagExt;
public int memberFlagExt2;
public int memberLevel;
public int memberMobileFlag;
public int memberTitleId;
public int mssVipType;
public int richFlag;
public int shutUpTime;
public long specialTitleExpireTime;
public long uin;
public int userShowFlag;
public int userShowFlagNew;
long serialVersionUID = 1;
public String uid = "";
public String qid = "";
public String nick = "";
public String remark = "";
public String cardName = "";
public MemberRole role = MemberRole.values()[0];
public String avatarPath = "";
public byte[] groupHonor = new byte[0];
public String memberSpecialTitle = "";
public String autoRemark = "";
public String getAutoRemark() {
return this.autoRemark;
} }
public String getAvatarPath() { public String getAvatarPath() {
return this.avatarPath; return this.avatarPath;
} }
public int getBigClubFlag() {
return this.bigClubFlag;
}
public int getBigClubLevel() {
return this.bigClubLevel;
}
public String getCardName() { public String getCardName() {
return this.cardName; return this.cardName;
} }
public int getCardNameId() {
return this.cardNameId;
}
public int getCardType() { public int getCardType() {
return this.cardType; return this.cardType;
} }
public int getCreditLevel() {
return this.creditLevel;
}
public int getGlobalGroupLevel() {
return this.globalGroupLevel;
}
public int getGlobalGroupPoint() {
return this.globalGroupPoint;
}
public byte[] getGroupHonor() {
return this.groupHonor;
}
public boolean getIsDelete() { public boolean getIsDelete() {
return this.isDelete; return this.isDelete;
} }
public boolean getIsRobot() {
return this.isRobot;
}
public boolean getIsSpecialConcerned() { public boolean getIsSpecialConcerned() {
return this.isSpecialConcerned; return this.isSpecialConcerned;
} }
public boolean getIsSpecialShielded() {
return this.isSpecialShielded;
}
public int getJoinTime() {
return this.joinTime;
}
public int getLastSpeakTime() {
return this.lastSpeakTime;
}
public int getMemberFlag() {
return this.memberFlag;
}
public int getMemberFlagExt() {
return this.memberFlagExt;
}
public int getMemberFlagExt2() {
return this.memberFlagExt2;
}
public int getMemberLevel() {
return this.memberLevel;
}
public int getMemberMobileFlag() {
return this.memberMobileFlag;
}
public String getMemberSpecialTitle() {
return this.memberSpecialTitle;
}
public int getMemberTitleId() {
return this.memberTitleId;
}
public int getMssVipType() {
return this.mssVipType;
}
public String getNick() { public String getNick() {
return this.nick; return this.nick;
} }
@ -58,6 +153,10 @@ public final class MemberInfo implements IKernelModel {
return this.remark; return this.remark;
} }
public int getRichFlag() {
return this.richFlag;
}
public MemberRole getRole() { public MemberRole getRole() {
return this.role; return this.role;
} }
@ -66,6 +165,10 @@ public final class MemberInfo implements IKernelModel {
return this.shutUpTime; return this.shutUpTime;
} }
public long getSpecialTitleExpireTime() {
return this.specialTitleExpireTime;
}
public String getUid() { public String getUid() {
return this.uid; return this.uid;
} }
@ -74,26 +177,114 @@ public final class MemberInfo implements IKernelModel {
return this.uin; return this.uin;
} }
public int getUserShowFlag() {
return this.userShowFlag;
}
public int getUserShowFlagNew() {
return this.userShowFlagNew;
}
public void setAutoRemark(String str) {
this.autoRemark = str;
}
public void setAvatarPath(String str) { public void setAvatarPath(String str) {
this.avatarPath = str; this.avatarPath = str;
} }
public void setBigClubFlag(int i2) {
this.bigClubFlag = i2;
}
public void setBigClubLevel(int i2) {
this.bigClubLevel = i2;
}
public void setCardName(String str) { public void setCardName(String str) {
this.cardName = str; this.cardName = str;
} }
public void setCardNameId(int i2) {
this.cardNameId = i2;
}
public void setCardType(int i2) { public void setCardType(int i2) {
this.cardType = i2; this.cardType = i2;
} }
public void setCreditLevel(int i2) {
this.creditLevel = i2;
}
public void setGlobalGroupLevel(int i2) {
this.globalGroupLevel = i2;
}
public void setGlobalGroupPoint(int i2) {
this.globalGroupPoint = i2;
}
public void setGroupHonor(byte[] bArr) {
this.groupHonor = bArr;
}
public void setIsDelete(boolean z) { public void setIsDelete(boolean z) {
this.isDelete = z; this.isDelete = z;
} }
public void setIsRobot(boolean z) {
this.isRobot = z;
}
public void setIsSpecialConcerned(boolean z) { public void setIsSpecialConcerned(boolean z) {
this.isSpecialConcerned = z; this.isSpecialConcerned = z;
} }
public void setIsSpecialShielded(boolean z) {
this.isSpecialShielded = z;
}
public void setJoinTime(int i2) {
this.joinTime = i2;
}
public void setLastSpeakTime(int i2) {
this.lastSpeakTime = i2;
}
public void setMemberFlag(int i2) {
this.memberFlag = i2;
}
public void setMemberFlagExt(int i2) {
this.memberFlagExt = i2;
}
public void setMemberFlagExt2(int i2) {
this.memberFlagExt2 = i2;
}
public void setMemberLevel(int i2) {
this.memberLevel = i2;
}
public void setMemberMobileFlag(int i2) {
this.memberMobileFlag = i2;
}
public void setMemberSpecialTitle(String str) {
this.memberSpecialTitle = str;
}
public void setMemberTitleId(int i2) {
this.memberTitleId = i2;
}
public void setMssVipType(int i2) {
this.mssVipType = i2;
}
public void setNick(String str) { public void setNick(String str) {
this.nick = str; this.nick = str;
} }
@ -106,6 +297,10 @@ public final class MemberInfo implements IKernelModel {
this.remark = str; this.remark = str;
} }
public void setRichFlag(int i2) {
this.richFlag = i2;
}
public void setRole(MemberRole memberRole) { public void setRole(MemberRole memberRole) {
this.role = memberRole; this.role = memberRole;
} }
@ -114,6 +309,10 @@ public final class MemberInfo implements IKernelModel {
this.shutUpTime = i2; this.shutUpTime = i2;
} }
public void setSpecialTitleExpireTime(long j2) {
this.specialTitleExpireTime = j2;
}
public void setUid(String str) { public void setUid(String str) {
this.uid = str; this.uid = str;
} }
@ -122,30 +321,16 @@ public final class MemberInfo implements IKernelModel {
this.uin = j2; this.uin = j2;
} }
public String toString() { public void setUserShowFlag(int i2) {
return "MemberInfo{uid=" + this.uid + ",qid=" + this.qid + ",uin=" + this.uin + ",nick=" + this.nick + ",remark=" + this.remark + ",cardType=" + this.cardType + ",cardName=" + this.cardName + ",role=" + this.role + ",avatarPath=" + this.avatarPath + ",shutUpTime=" + this.shutUpTime + ",isDelete=" + this.isDelete + ",isSpecialConcerned=" + this.isSpecialConcerned + ",}"; this.userShowFlag = i2;
} }
public MemberInfo(String str, String str2, long j2, String str3, String str4, int i2, String str5, MemberRole memberRole, String str6, int i3, boolean z, boolean z2) { public void setUserShowFlagNew(int i2) {
this.serialVersionUID = 1L; this.userShowFlagNew = i2;
this.uid = "";
this.qid = "";
this.nick = "";
this.remark = "";
this.cardName = "";
this.role = MemberRole.values()[0];
this.avatarPath = "";
this.uid = str;
this.qid = str2;
this.uin = j2;
this.nick = str3;
this.remark = str4;
this.cardType = i2;
this.cardName = str5;
this.role = memberRole;
this.avatarPath = str6;
this.shutUpTime = i3;
this.isDelete = z;
this.isSpecialConcerned = z2;
} }
public String toString() {
return "MemberInfo{uid=" + this.uid + ",qid=" + this.qid + ",uin=" + this.uin + ",nick=" + this.nick + ",remark=" + this.remark + ",cardType=" + this.cardType + ",cardName=" + this.cardName + ",role=" + this.role + ",avatarPath=" + this.avatarPath + ",shutUpTime=" + this.shutUpTime + ",isDelete=" + this.isDelete + ",isSpecialConcerned=" + this.isSpecialConcerned + ",isRobot=" + this.isRobot + ",groupHonor=" + this.groupHonor + ",memberLevel=" + this.memberLevel + ",globalGroupLevel=" + this.globalGroupLevel + ",globalGroupPoint=" + this.globalGroupPoint + ",memberTitleId=" + this.memberTitleId + ",memberSpecialTitle=" + this.memberSpecialTitle + ",specialTitleExpireTime=" + this.specialTitleExpireTime + ",userShowFlag=" + this.userShowFlag + ",userShowFlagNew=" + this.userShowFlagNew + ",richFlag=" + this.richFlag + ",mssVipType=" + this.mssVipType + ",bigClubLevel=" + this.bigClubLevel + ",bigClubFlag=" + this.bigClubFlag + ",autoRemark=" + this.autoRemark + ",creditLevel=" + this.creditLevel + ",joinTime=" + this.joinTime + ",lastSpeakTime=" + this.lastSpeakTime + ",memberFlag=" + this.memberFlag + ",memberFlagExt=" + this.memberFlagExt + ",memberMobileFlag=" + this.memberMobileFlag + ",memberFlagExt2=" + this.memberFlagExt2 + ",isSpecialShielded=" + this.isSpecialShielded + ",cardNameId=" + this.cardNameId + ",}";
}
} }

View File

@ -0,0 +1,19 @@
package com.tencent.qqnt.kernel.nativeinterface;
public class MemberLevelName {
public int level;
public String strName = "";
public int getLevel() {
return this.level;
}
public String getStrName() {
return this.strName;
}
public String toString() {
return "MemberLevelName{level=" + this.level + ",strName=" + this.strName + ",}";
}
}

View File

@ -1,5 +1,7 @@
package com.tencent.qqnt.kernel.nativeinterface; package com.tencent.qqnt.kernel.nativeinterface;
import com.tencent.qqnt.kernelpublic.nativeinterface.Contact;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;

View File

@ -0,0 +1,4 @@
package com.tencent.qqnt.kernel.nativeinterface;
public class QueryUserSecQualityRsp {
}

View File

@ -1,6 +1,7 @@
package com.tencent.qqnt.kernel.nativeinterface; package com.tencent.qqnt.kernel.nativeinterface;
import com.tencent.qqnt.kernelpublic.nativeinterface.Contact;
public final class UnreadCntInfo { public final class UnreadCntInfo {
UnreadCnt allUnreadCnt; UnreadCnt allUnreadCnt;

View File

@ -1,4 +1,6 @@
package com.tencent.qqnt.kernel.nativeinterface; package com.tencent.qqnt.kernelpublic.nativeinterface;
import com.tencent.qqnt.kernel.nativeinterface.IKernelModel;
import java.io.Serializable; import java.io.Serializable;

View File

@ -0,0 +1,9 @@
package com.tencent.qqnt.kernelpublic.nativeinterface;
public enum MemberRole {
UNSPECIFIED,
STRANGER,
MEMBER,
ADMIN,
OWNER
}

View File

@ -2,6 +2,7 @@ package com.tencent.qqnt.msg.api;
import com.tencent.mobileqq.qroute.QRouteApi; import com.tencent.mobileqq.qroute.QRouteApi;
import com.tencent.qqnt.kernel.nativeinterface.*; import com.tencent.qqnt.kernel.nativeinterface.*;
import com.tencent.qqnt.kernelpublic.nativeinterface.Contact;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -9,7 +10,6 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import kotlin.Pair;
import kotlinx.coroutines.flow.Flow; import kotlinx.coroutines.flow.Flow;
public interface IMsgService extends QRouteApi { public interface IMsgService extends QRouteApi {

View File

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

View File

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

View File

@ -34,7 +34,7 @@ active_ticket=
enable_self_message=false enable_self_message=false
# 旧BDH兼容开关 # 旧BDH兼容开关
enable_old_bdh=false enable_old_bdh=true
# 反JVM调用栈跟踪 # 反JVM调用栈跟踪
anti_jvm_trace=true anti_jvm_trace=true

View File

@ -0,0 +1,164 @@
@file:OptIn(DelicateCoroutinesApi::class)
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.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
import kotlinx.coroutines.Job
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.launch
import kritor.handlers.handleGrpc
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.internals.GlobalEventTransmitter
import moe.fuqiuluo.shamrock.tools.ShamrockVersion
import qq.service.ticket.TicketHelper
import kotlin.time.Duration.Companion.seconds
internal class KritorClient(
val host: String,
val port: Int
) {
private lateinit var channel: ManagedChannel
private lateinit var channelJob: Job
private val senderChannel = MutableSharedFlow<Response>()
fun start() {
runCatching {
if (::channel.isInitialized && isActive()){
channel.shutdown()
}
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)
}
}
fun listen(retryCnt: Int = -1) {
if (::channelJob.isInitialized && channelJob.isActive) {
channelJob.cancel()
}
channelJob = GlobalScope.launch(Dispatchers.IO) {
runCatching {
val stub = ReverseServiceGrpcKt.ReverseServiceCoroutineStub(channel)
registerEvent(EventType.EVENT_TYPE_MESSAGE)
registerEvent(EventType.EVENT_TYPE_CORE_EVENT)
registerEvent(EventType.EVENT_TYPE_REQUEST)
registerEvent(EventType.EVENT_TYPE_NOTICE)
stub.reverseStream(channelFlow {
senderChannel.collect { send(it) }
}).collect {
onReceive(it)
}
}.onFailure {
LogCenter.log("KritorClient listen failed, retry after 15s: ${it.stackTraceToString()}", Level.WARN)
}
delay(15.seconds)
LogCenter.log("KritorClient listen retrying, retryCnt = $retryCnt", Level.WARN)
if (retryCnt != 0) listen(retryCnt - 1)
}
}
fun registerEvent(eventType: EventType) {
GlobalScope.launch(Dispatchers.IO) {
runCatching {
EventServiceGrpcKt.EventServiceCoroutineStub(channel).registerPassiveListener(channelFlow {
when(eventType) {
EventType.EVENT_TYPE_MESSAGE -> GlobalEventTransmitter.onMessageEvent {
send(EventStructure.newBuilder().apply {
this.type = EventType.EVENT_TYPE_MESSAGE
this.message = it.second
}.build())
}
EventType.EVENT_TYPE_CORE_EVENT -> {}
EventType.EVENT_TYPE_NOTICE -> GlobalEventTransmitter.onNoticeEvent {
send(EventStructure.newBuilder().apply {
this.type = EventType.EVENT_TYPE_NOTICE
this.notice = it
}.build())
}
EventType.EVENT_TYPE_REQUEST -> GlobalEventTransmitter.onRequestEvent {
send(EventStructure.newBuilder().apply {
this.type = EventType.EVENT_TYPE_REQUEST
this.request = it
}.build())
}
EventType.UNRECOGNIZED -> {}
}
})
}.onFailure {
LogCenter.log("KritorClient registerEvent failed: ${it.stackTraceToString()}", Level.ERROR)
}
}
}
private suspend fun onReceive(request: Request) = GlobalScope.launch {
//LogCenter.log("KritorClient onReceive: $request")
runCatching {
val rsp = handleGrpc(request.cmd, request.buf.toByteArray())
senderChannel.emit(Response.newBuilder()
.setCmd(request.cmd)
.setCode(Response.ResponseCode.SUCCESS)
.setMsg("success")
.setSeq(request.seq)
.setBuf(ByteString.copyFrom(rsp))
.build())
}.onFailure {
senderChannel.emit(Response.newBuilder()
.setCmd(request.cmd)
.setCode(Response.ResponseCode.INTERNAL)
.setMsg(it.stackTraceToString())
.setSeq(request.seq)
.setBuf(ByteString.EMPTY)
.build())
}
}
fun isActive(): Boolean {
return !channel.isShutdown
}
fun close() {
channel.shutdown()
}
}

View File

@ -0,0 +1,6 @@
package kritor.handlers
internal object GrpcHandlers {
}

View File

@ -2,7 +2,12 @@
package kritor.server package kritor.server
import io.grpc.Grpc import io.grpc.Grpc
import io.grpc.Metadata
import io.grpc.InsecureServerCredentials 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.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -10,22 +15,45 @@ import kotlinx.coroutines.asExecutor
import kritor.auth.AuthInterceptor import kritor.auth.AuthInterceptor
import kritor.service.* import kritor.service.*
import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.tools.ShamrockVersion
import qq.service.ticket.TicketHelper
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class KritorServer( class KritorServer(
private val port: Int private val port: Int
): CoroutineScope { ): 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()) private val server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create())
.executor(Dispatchers.IO.asExecutor()) .executor(Dispatchers.IO.asExecutor())
.intercept(AuthInterceptor) .intercept(AuthInterceptor)
.addService(Authentication) .intercept(serverInterceptor)
.addService(ContactService) .addService(AuthenticationService)
.addService(KritorService) .addService(CoreService)
.addService(FriendService) .addService(FriendService)
.addService(GroupService) .addService(GroupService)
.addService(GroupFileService) .addService(GroupFileService)
.addService(MessageService) .addService(MessageService)
.addService(EventService) .addService(EventService)
.addService(WebService)
.addService(DeveloperService)
.addService(QsignService)
.build()!! .build()!!
fun start(block: Boolean = false) { fun start(block: Boolean = false) {

View File

@ -1,66 +0,0 @@
package kritor.service
import io.grpc.Status
import io.grpc.StatusRuntimeException
import io.kritor.AuthCode
import io.kritor.AuthReq
import io.kritor.AuthRsp
import io.kritor.AuthenticationGrpcKt
import io.kritor.GetAuthStateReq
import io.kritor.GetAuthStateRsp
import io.kritor.authRsp
import io.kritor.getAuthStateRsp
import kritor.auth.AuthInterceptor
import moe.fuqiuluo.shamrock.config.ActiveTicket
import moe.fuqiuluo.shamrock.config.ShamrockConfig
import qq.service.QQInterfaces
object Authentication: AuthenticationGrpcKt.AuthenticationCoroutineImplBase() {
@Grpc("Authentication", "Auth")
override suspend fun auth(request: AuthReq): AuthRsp {
if (QQInterfaces.app.account != request.account) {
return authRsp {
code = AuthCode.NO_ACCOUNT
msg = "No such account"
}
}
val activeTicketName = ActiveTicket.name()
var index = 0
while (true) {
val ticket = ShamrockConfig.getProperty(activeTicketName + if (index == 0) "" else ".$index", null)
if (ticket.isNullOrEmpty()) {
if (index == 0) {
return authRsp {
code = AuthCode.OK
msg = "OK"
}
} else {
break
}
} else if (ticket == request.ticket) {
return authRsp {
code = AuthCode.OK
msg = "OK"
}
}
index++
}
return authRsp {
code = AuthCode.NO_TICKET
msg = "Invalid ticket"
}
}
@Grpc("Authentication", "GetAuthState")
override suspend fun getAuthState(request: GetAuthStateReq): GetAuthStateRsp {
if (request.account != QQInterfaces.app.account) {
throw StatusRuntimeException(Status.CANCELLED.withDescription("No such account"))
}
return getAuthStateRsp {
isRequiredAuth = AuthInterceptor.getAllTicket().isNotEmpty()
}
}
}

View File

@ -0,0 +1,60 @@
package kritor.service
import io.grpc.Status
import io.grpc.StatusRuntimeException
import io.kritor.authentication.*
import io.kritor.authentication.AuthenticateResponse.AuthenticateResponseCode
import kritor.auth.AuthInterceptor
import moe.fuqiuluo.shamrock.config.ActiveTicket
import moe.fuqiuluo.shamrock.config.ShamrockConfig
import qq.service.QQInterfaces
internal object AuthenticationService: AuthenticationServiceGrpcKt.AuthenticationServiceCoroutineImplBase() {
@Grpc("AuthenticationService", "Authenticate")
override suspend fun authenticate(request: AuthenticateRequest): AuthenticateResponse {
if (QQInterfaces.app.account != request.account) {
return AuthenticateResponse.newBuilder().apply {
code = AuthenticateResponseCode.NO_ACCOUNT
msg = "No such account"
}.build()
}
val activeTicketName = ActiveTicket.name()
var index = 0
while (true) {
val ticket = ShamrockConfig.getProperty(activeTicketName + if (index == 0) "" else ".$index", null)
if (ticket.isNullOrEmpty()) {
if (index == 0) {
return AuthenticateResponse.newBuilder().apply {
code = AuthenticateResponseCode.OK
msg = "OK"
}.build()
} else {
break
}
} else if (ticket == request.ticket) {
return AuthenticateResponse.newBuilder().apply {
code = AuthenticateResponseCode.OK
msg = "OK"
}.build()
}
index++
}
return AuthenticateResponse.newBuilder().apply {
code = AuthenticateResponseCode.NO_TICKET
msg = "Invalid ticket"
}.build()
}
@Grpc("AuthenticationService", "GetAuthenticationState")
override suspend fun getAuthenticationState(request: GetAuthenticationStateRequest): GetAuthenticationStateResponse {
if (request.account != QQInterfaces.app.account) {
throw StatusRuntimeException(Status.CANCELLED.withDescription("No such account"))
}
return GetAuthenticationStateResponse.newBuilder().apply {
isRequired = AuthInterceptor.getAllTicket().isNotEmpty()
}.build()
}
}

View File

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

View File

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

View File

@ -0,0 +1,67 @@
package kritor.service
import com.google.protobuf.ByteString
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", "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", "ClearCache")
override suspend fun clearCache(request: ClearCacheRequest): ClearCacheResponse {
FileUtils.clearCache()
MMKVFetcher.mmkvWithId("audio2silk")
.clear()
return ClearCacheResponse.newBuilder().build()
}
@Grpc("DeveloperService", "GetDeviceBattery")
override suspend fun getDeviceBattery(request: GetDeviceBatteryRequest): GetDeviceBatteryResponse {
return GetDeviceBatteryResponse.newBuilder().apply {
PlatformUtils.getDeviceBattery().let {
this.battery = it.battery
this.scale = it.scale
this.status = it.status
}
}.build()
}
@Grpc("DeveloperService", "SendPacket")
override suspend fun sendPacket(request: SendPacketRequest): SendPacketResponse {
return SendPacketResponse.newBuilder().apply {
val fromServiceMsg = QQInterfaces.sendBufferAW(request.command, request.isProtobuf, request.requestBuffer.toByteArray())
if (fromServiceMsg?.wupBuffer == null) {
this.isSuccess = false
} else {
this.isSuccess = true
this.responseBuffer = ByteString.copyFrom(fromServiceMsg.wupBuffer)
}
}.build()
}
}

View File

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

View File

@ -1,28 +1,33 @@
package kritor.service package kritor.service
import android.os.Bundle
import com.tencent.mobileqq.profilecard.api.IProfileCardBlacklistApi
import com.tencent.mobileqq.profilecard.api.IProfileProtocolConst
import com.tencent.mobileqq.profilecard.api.IProfileProtocolService
import com.tencent.mobileqq.qroute.QRoute
import io.grpc.Status import io.grpc.Status
import io.grpc.StatusRuntimeException import io.grpc.StatusRuntimeException
import io.kritor.friend.FriendServiceGrpcKt import io.kritor.friend.*
import io.kritor.friend.GetFriendListRequest import kotlinx.coroutines.suspendCancellableCoroutine
import io.kritor.friend.GetFriendListResponse import kotlinx.coroutines.withTimeoutOrNull
import io.kritor.friend.friendData import qq.service.QQInterfaces
import io.kritor.friend.friendExt
import io.kritor.friend.getFriendListResponse
import qq.service.contact.ContactHelper import qq.service.contact.ContactHelper
import qq.service.friend.FriendHelper import qq.service.friend.FriendHelper
import kotlin.coroutines.resume
object FriendService: FriendServiceGrpcKt.FriendServiceCoroutineImplBase() { internal object FriendService : FriendServiceGrpcKt.FriendServiceCoroutineImplBase() {
@Grpc("FriendService", "GetFriendList") @Grpc("FriendService", "GetFriendList")
override suspend fun getFriendList(request: GetFriendListRequest): GetFriendListResponse { override suspend fun getFriendList(request: GetFriendListRequest): GetFriendListResponse {
val friendList = FriendHelper.getFriendList(if(request.hasRefresh()) request.refresh else false).onFailure { val friendList = FriendHelper.getFriendList(if (request.hasRefresh()) request.refresh else false).onFailure {
throw StatusRuntimeException(Status.INTERNAL throw StatusRuntimeException(
Status.INTERNAL
.withDescription(it.stackTraceToString()) .withDescription(it.stackTraceToString())
) )
}.getOrThrow() }.getOrThrow()
return getFriendListResponse { return GetFriendListResponse.newBuilder().apply {
friendList.forEach { friendList.forEach {
this.friendList.add(friendData { this.addFriendsInfo(FriendInfo.newBuilder().apply {
uin = it.uin.toLong() uin = it.uin.toLong()
uid = ContactHelper.getUidByUinAsync(uin) uid = ContactHelper.getUidByUinAsync(uin)
qid = "" qid = ""
@ -32,10 +37,208 @@ object FriendService: FriendServiceGrpcKt.FriendServiceCoroutineImplBase() {
level = 0 level = 0
gender = it.gender.toInt() gender = it.gender.toInt()
groupId = it.groupid groupId = it.groupid
ext = friendExt {}.toByteString()
ext = ExtInfo.newBuilder().build()
}) })
} }
} }.build()
} }
@Grpc("FriendService", "GetFriendProfileCard")
override suspend fun getFriendProfileCard(request: GetFriendProfileCardRequest): GetFriendProfileCardResponse {
return GetFriendProfileCardResponse.newBuilder().apply {
request.targetUinsList.forEach {
ContactHelper.getProfileCard(it).getOrThrow().let { info ->
addFriendsProfileCard(ProfileCard.newBuilder().apply {
this.uin = info.uin.toLong()
this.uid = ContactHelper.getUidByUinAsync(info.uin.toLong())
this.nick = info.strNick
this.remark = info.strReMark
this.level = info.iQQLevel
this.birthday = info.lBirthday
this.loginDay = info.lLoginDays.toInt()
this.voteCnt = info.lVoteCount.toInt()
this.qid = info.qid ?: ""
this.isSchoolVerified = info.schoolVerifiedFlag
this.ext = ExtInfo.newBuilder().apply {
this.bigVip = info.bBigClubVipOpen == 1.toByte()
this.hollywoodVip = info.bHollywoodVipOpen == 1.toByte()
this.qqVip = info.bQQVipOpen == 1.toByte()
this.superVip = info.bSuperQQOpen == 1.toByte()
this.voted = info.bVoted == 1.toByte()
}.build()
}.build())
}
}
request.targetUidsList.forEach {
ContactHelper.getProfileCard(ContactHelper.getUinByUidAsync(it).toLong()).getOrThrow().let { info ->
addFriendsProfileCard(ProfileCard.newBuilder().apply {
this.uin = info.uin.toLong()
this.uid = it
this.nick = info.strNick
this.remark = info.strReMark
this.level = info.iQQLevel
this.birthday = info.lBirthday
this.loginDay = info.lLoginDays.toInt()
this.voteCnt = info.lVoteCount.toInt()
this.qid = info.qid ?: ""
this.isSchoolVerified = info.schoolVerifiedFlag
this.ext = ExtInfo.newBuilder().apply {
this.bigVip = info.bBigClubVipOpen == 1.toByte()
this.hollywoodVip = info.bHollywoodVipOpen == 1.toByte()
this.qqVip = info.bQQVipOpen == 1.toByte()
this.superVip = info.bSuperQQOpen == 1.toByte()
this.voted = info.bVoted == 1.toByte()
}.build()
}.build())
}
}
}.build()
}
@Grpc("FriendService", "GetStrangerProfileCard")
override suspend fun getStrangerProfileCard(request: GetStrangerProfileCardRequest): GetStrangerProfileCardResponse {
return GetStrangerProfileCardResponse.newBuilder().apply {
request.targetUinsList.forEach {
ContactHelper.refreshAndGetProfileCard(it).getOrThrow().let { info ->
addStrangersProfileCard(ProfileCard.newBuilder().apply {
this.uin = info.uin.toLong()
this.uid = ContactHelper.getUidByUinAsync(info.uin.toLong())
this.nick = info.strNick
this.level = info.iQQLevel
this.birthday = info.lBirthday
this.loginDay = info.lLoginDays.toInt()
this.voteCnt = info.lVoteCount.toInt()
this.qid = info.qid ?: ""
this.isSchoolVerified = info.schoolVerifiedFlag
this.ext = ExtInfo.newBuilder().apply {
this.bigVip = info.bBigClubVipOpen == 1.toByte()
this.hollywoodVip = info.bHollywoodVipOpen == 1.toByte()
this.qqVip = info.bQQVipOpen == 1.toByte()
this.superVip = info.bSuperQQOpen == 1.toByte()
this.voted = info.bVoted == 1.toByte()
}.build()
}.build())
}
}
request.targetUidsList.forEach {
ContactHelper.refreshAndGetProfileCard(ContactHelper.getUinByUidAsync(it).toLong()).getOrThrow()
.let { info ->
addStrangersProfileCard(ProfileCard.newBuilder().apply {
this.uin = info.uin.toLong()
this.uid = it
this.nick = info.strNick
this.level = info.iQQLevel
this.birthday = info.lBirthday
this.loginDay = info.lLoginDays.toInt()
this.voteCnt = info.lVoteCount.toInt()
this.qid = info.qid ?: ""
this.isSchoolVerified = info.schoolVerifiedFlag
this.ext = ExtInfo.newBuilder().apply {
this.bigVip = info.bBigClubVipOpen == 1.toByte()
this.hollywoodVip = info.bHollywoodVipOpen == 1.toByte()
this.qqVip = info.bQQVipOpen == 1.toByte()
this.superVip = info.bSuperQQOpen == 1.toByte()
this.voted = info.bVoted == 1.toByte()
}.build()
}.build())
}
}
}.build()
}
@Grpc("FriendService", "SetProfileCard")
override suspend fun setProfileCard(request: SetProfileCardRequest): SetProfileCardResponse {
val bundle = Bundle()
val service = QQInterfaces.app
.getRuntimeService(IProfileProtocolService::class.java, "all")
if (request.hasNick()) {
bundle.putString(IProfileProtocolConst.KEY_NICK, request.nick)
}
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

@ -3,6 +3,7 @@ package kritor.service
import io.grpc.Status import io.grpc.Status
import io.grpc.StatusRuntimeException import io.grpc.StatusRuntimeException
import io.kritor.file.* import io.kritor.file.*
import moe.fuqiuluo.shamrock.tools.decodeToOidb
import moe.fuqiuluo.shamrock.tools.slice import moe.fuqiuluo.shamrock.tools.slice
import moe.fuqiuluo.symbols.decodeProtobuf import moe.fuqiuluo.symbols.decodeProtobuf
import protobuf.auto.toByteArray import protobuf.auto.toByteArray
@ -15,10 +16,9 @@ import qq.service.QQInterfaces
import qq.service.file.GroupFileHelper import qq.service.file.GroupFileHelper
import qq.service.file.GroupFileHelper.getGroupFileSystemInfo import qq.service.file.GroupFileHelper.getGroupFileSystemInfo
import tencent.im.oidb.cmd0x6d6.oidb_0x6d6 import tencent.im.oidb.cmd0x6d6.oidb_0x6d6
import tencent.im.oidb.cmd0x6d8.oidb_0x6d8
import tencent.im.oidb.oidb_sso import tencent.im.oidb.oidb_sso
internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCoroutineImplBase() { internal object GroupFileService : GroupFileServiceGrpcKt.GroupFileServiceCoroutineImplBase() {
@Grpc("GroupFileService", "CreateFolder") @Grpc("GroupFileService", "CreateFolder")
override suspend fun createFolder(request: CreateFolderRequest): CreateFolderResponse { override suspend fun createFolder(request: CreateFolderRequest): CreateFolderResponse {
val data = Oidb0x6d7ReqBody( val data = Oidb0x6d7ReqBody(
@ -34,39 +34,39 @@ internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCorouti
if (fromServiceMsg.wupBuffer == null) { if (fromServiceMsg.wupBuffer == null) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed")) throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
} }
val oidbPkg = oidb_sso.OIDBSSOPkg() val oidbPkg = fromServiceMsg.decodeToOidb()
oidbPkg.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
val rsp = oidbPkg.bytes_bodybuffer.get() val rsp = oidbPkg.bytes_bodybuffer.get()
.toByteArray() .toByteArray()
.decodeProtobuf<Oidb0x6d7RespBody>() .decodeProtobuf<Oidb0x6d7RespBody>()
if (rsp.createFolder?.retCode != 0) { if (rsp.createFolder?.retCode != 0) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to create folder: ${rsp.createFolder?.retCode}")) throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to create folder: ${rsp.createFolder?.retCode}"))
} }
return createFolderResponse { return CreateFolderResponse.newBuilder().apply {
this.id = rsp.createFolder?.folderInfo?.folderId ?: "" this.id = rsp.createFolder?.folderInfo?.folderId ?: ""
this.usedSpace = 0 this.usedSpace = 0
} }.build()
} }
@Grpc("GroupFileService", "DeleteFolder") @Grpc("GroupFileService", "DeleteFolder")
override suspend fun deleteFolder(request: DeleteFolderRequest): DeleteFolderResponse { override suspend fun deleteFolder(request: DeleteFolderRequest): DeleteFolderResponse {
val fromServiceMsg = QQInterfaces.sendOidbAW("OidbSvc.0x6d7_1", 1751, 1, Oidb0x6d7ReqBody( val fromServiceMsg = QQInterfaces.sendOidbAW(
"OidbSvc.0x6d7_1", 1751, 1, Oidb0x6d7ReqBody(
deleteFolder = DeleteFolderReq( deleteFolder = DeleteFolderReq(
groupCode = request.groupId.toULong(), groupCode = request.groupId.toULong(),
appId = 3u, appId = 3u,
folderId = request.folderId folderId = request.folderId
) )
).toByteArray()) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request")) ).toByteArray()
) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
if (fromServiceMsg.wupBuffer == null) { if (fromServiceMsg.wupBuffer == null) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed")) throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
} }
val oidbPkg = oidb_sso.OIDBSSOPkg() val oidbPkg = fromServiceMsg.decodeToOidb()
oidbPkg.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>() val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>()
if (rsp.deleteFolder?.retCode != 0) { if (rsp.deleteFolder?.retCode != 0) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to delete folder: ${rsp.deleteFolder?.retCode}")) throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to delete folder: ${rsp.deleteFolder?.retCode}"))
} }
return deleteFolderResponse { } return DeleteFolderResponse.newBuilder().build()
} }
@Grpc("GroupFileService", "DeleteFile") @Grpc("GroupFileService", "DeleteFile")
@ -85,54 +85,49 @@ internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCorouti
if (fromServiceMsg.wupBuffer == null) { if (fromServiceMsg.wupBuffer == null) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed")) throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
} }
val oidbPkg = oidb_sso.OIDBSSOPkg() val oidbPkg = fromServiceMsg.decodeToOidb()
oidbPkg.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
val rsp = oidb_0x6d6.RspBody().apply { val rsp = oidb_0x6d6.RspBody().apply {
mergeFrom(oidbPkg.bytes_bodybuffer.get().toByteArray()) mergeFrom(oidbPkg.bytes_bodybuffer.get().toByteArray())
} }
if (rsp.delete_file_rsp.int32_ret_code.get() != 0) { if (rsp.delete_file_rsp.int32_ret_code.get() != 0) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to delete file: ${rsp.delete_file_rsp.int32_ret_code.get()}")) throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to delete file: ${rsp.delete_file_rsp.int32_ret_code.get()}"))
} }
return deleteFileResponse { } return DeleteFileResponse.newBuilder().build()
} }
@Grpc("GroupFileService", "RenameFolder") @Grpc("GroupFileService", "RenameFolder")
override suspend fun renameFolder(request: RenameFolderRequest): RenameFolderResponse { override suspend fun renameFolder(request: RenameFolderRequest): RenameFolderResponse {
val fromServiceMsg = QQInterfaces.sendOidbAW("OidbSvc.0x6d7_3", 1751, 3, Oidb0x6d7ReqBody( val fromServiceMsg = QQInterfaces.sendOidbAW(
"OidbSvc.0x6d7_3", 1751, 3, Oidb0x6d7ReqBody(
renameFolder = RenameFolderReq( renameFolder = RenameFolderReq(
groupCode = request.groupId.toULong(), groupCode = request.groupId.toULong(),
appId = 3u, appId = 3u,
folderId = request.folderId, folderId = request.folderId,
folderName = request.name folderName = request.name
) )
).toByteArray()) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request")) ).toByteArray()
) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
if (fromServiceMsg.wupBuffer == null) { if (fromServiceMsg.wupBuffer == null) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed")) throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
} }
val oidbPkg = oidb_sso.OIDBSSOPkg() val oidbPkg = fromServiceMsg.decodeToOidb()
oidbPkg.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>() val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>()
if (rsp.renameFolder?.retCode != 0) { if (rsp.renameFolder?.retCode != 0) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to rename folder: ${rsp.renameFolder?.retCode}")) throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to rename folder: ${rsp.renameFolder?.retCode}"))
} }
return renameFolderResponse { } return RenameFolderResponse.newBuilder().build()
} }
@Grpc("GroupFileService", "GetFileSystemInfo")
override suspend fun getFileSystemInfo(request: GetFileSystemInfoRequest): GetFileSystemInfoResponse { override suspend fun getFileSystemInfo(request: GetFileSystemInfoRequest): GetFileSystemInfoResponse {
return getGroupFileSystemInfo(request.groupId) return getGroupFileSystemInfo(request.groupId)
} }
@Grpc("GroupFileService", "GetRootFiles") @Grpc("GroupFileService", "GetFileList")
override suspend fun getRootFiles(request: GetRootFilesRequest): GetRootFilesResponse { override suspend fun getFileList(request: GetFileListRequest): GetFileListResponse {
return getRootFilesResponse { return if (request.hasFolderId())
val response = GroupFileHelper.getGroupFiles(request.groupId) GroupFileHelper.getGroupFiles(request.groupId, request.folderId)
this.files.addAll(response.filesList) else
this.folders.addAll(response.foldersList) GroupFileHelper.getGroupFiles(request.groupId)
}
}
@Grpc("GroupFileService", "GetFiles")
override suspend fun getFiles(request: GetFilesRequest): GetFilesResponse {
return GroupFileHelper.getGroupFiles(request.groupId, request.folderId)
} }
} }

View File

@ -2,213 +2,188 @@ package kritor.service
import io.grpc.Status import io.grpc.Status
import io.grpc.StatusRuntimeException import io.grpc.StatusRuntimeException
import io.kritor.group.BanMemberRequest import io.kritor.group.*
import io.kritor.group.BanMemberResponse import moe.fuqiuluo.shamrock.helper.LogCenter
import io.kritor.group.GetGroupHonorRequest
import io.kritor.group.GetGroupHonorResponse
import io.kritor.group.GetGroupInfoRequest
import io.kritor.group.GetGroupInfoResponse
import io.kritor.group.GetGroupListRequest
import io.kritor.group.GetGroupListResponse
import io.kritor.group.GetGroupMemberInfoRequest
import io.kritor.group.GetGroupMemberInfoResponse
import io.kritor.group.GetGroupMemberListRequest
import io.kritor.group.GetGroupMemberListResponse
import io.kritor.group.GetNotJoinedGroupInfoRequest
import io.kritor.group.GetNotJoinedGroupInfoResponse
import io.kritor.group.GetProhibitedUserListRequest
import io.kritor.group.GetProhibitedUserListResponse
import io.kritor.group.GetRemainCountAtAllRequest
import io.kritor.group.GetRemainCountAtAllResponse
import io.kritor.group.GroupServiceGrpcKt
import io.kritor.group.KickMemberRequest
import io.kritor.group.KickMemberResponse
import io.kritor.group.LeaveGroupRequest
import io.kritor.group.LeaveGroupResponse
import io.kritor.group.ModifyGroupNameRequest
import io.kritor.group.ModifyGroupNameResponse
import io.kritor.group.ModifyGroupRemarkRequest
import io.kritor.group.ModifyGroupRemarkResponse
import io.kritor.group.ModifyMemberCardRequest
import io.kritor.group.ModifyMemberCardResponse
import io.kritor.group.PokeMemberRequest
import io.kritor.group.PokeMemberResponse
import io.kritor.group.SetGroupAdminRequest
import io.kritor.group.SetGroupAdminResponse
import io.kritor.group.SetGroupUniqueTitleRequest
import io.kritor.group.SetGroupUniqueTitleResponse
import io.kritor.group.SetGroupWholeBanRequest
import io.kritor.group.SetGroupWholeBanResponse
import io.kritor.group.banMemberResponse
import io.kritor.group.getGroupHonorResponse
import io.kritor.group.getGroupInfoResponse
import io.kritor.group.getGroupListResponse
import io.kritor.group.getGroupMemberInfoResponse
import io.kritor.group.getGroupMemberListResponse
import io.kritor.group.getNotJoinedGroupInfoResponse
import io.kritor.group.getProhibitedUserListResponse
import io.kritor.group.getRemainCountAtAllResponse
import io.kritor.group.groupHonorInfo
import io.kritor.group.groupMemberInfo
import io.kritor.group.kickMemberResponse
import io.kritor.group.leaveGroupResponse
import io.kritor.group.modifyGroupNameResponse
import io.kritor.group.modifyGroupRemarkResponse
import io.kritor.group.modifyMemberCardResponse
import io.kritor.group.notJoinedGroupInfo
import io.kritor.group.pokeMemberResponse
import io.kritor.group.prohibitedUserInfo
import io.kritor.group.setGroupAdminResponse
import io.kritor.group.setGroupUniqueTitleResponse
import io.kritor.group.setGroupWholeBanResponse
import moe.fuqiuluo.shamrock.helper.TroopHonorHelper
import moe.fuqiuluo.shamrock.helper.TroopHonorHelper.decodeHonor import moe.fuqiuluo.shamrock.helper.TroopHonorHelper.decodeHonor
import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
import qq.service.contact.ContactHelper import qq.service.contact.ContactHelper
import qq.service.group.GroupHelper import qq.service.group.GroupHelper
import tencent.im.troop.honor.troop_honor
internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase() { internal object GroupService : GroupServiceGrpcKt.GroupServiceCoroutineImplBase() {
@Grpc("GroupService", "BanMember") @Grpc("GroupService", "BanMember")
override suspend fun banMember(request: BanMemberRequest): BanMemberResponse { override suspend fun banMember(request: BanMemberRequest): BanMemberResponse {
if (!GroupHelper.isAdmin(request.groupId.toString())) { if (!GroupHelper.isAdmin(request.groupId.toString())) {
throw StatusRuntimeException(Status.PERMISSION_DENIED throw StatusRuntimeException(
Status.PERMISSION_DENIED
.withDescription("You are not admin of this group") .withDescription("You are not admin of this group")
) )
} }
GroupHelper.banMember(request.groupId, when(request.targetCase!!) { GroupHelper.banMember(
request.groupId, when (request.targetCase!!) {
BanMemberRequest.TargetCase.TARGET_UIN -> request.targetUin BanMemberRequest.TargetCase.TARGET_UIN -> request.targetUin
BanMemberRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong() BanMemberRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
else -> throw StatusRuntimeException(Status.INVALID_ARGUMENT else -> throw StatusRuntimeException(
Status.INVALID_ARGUMENT
.withDescription("target not set") .withDescription("target not set")
) )
}, request.duration) }, request.duration
)
return banMemberResponse { return BanMemberResponse.newBuilder().apply {
groupId = request.groupId groupId = request.groupId
} }.build()
} }
@Grpc("GroupService", "PokeMember") @Grpc("GroupService", "PokeMember")
override suspend fun pokeMember(request: PokeMemberRequest): PokeMemberResponse { override suspend fun pokeMember(request: PokeMemberRequest): PokeMemberResponse {
GroupHelper.pokeMember(request.groupId, when(request.targetCase!!) { GroupHelper.pokeMember(
request.groupId, when (request.targetCase!!) {
PokeMemberRequest.TargetCase.TARGET_UIN -> request.targetUin PokeMemberRequest.TargetCase.TARGET_UIN -> request.targetUin
PokeMemberRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong() PokeMemberRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
else -> throw StatusRuntimeException(Status.INVALID_ARGUMENT else -> throw StatusRuntimeException(
Status.INVALID_ARGUMENT
.withDescription("target not set") .withDescription("target not set")
) )
}) }
return pokeMemberResponse { } )
return PokeMemberResponse.newBuilder().build()
} }
@Grpc("GroupService", "KickMember") @Grpc("GroupService", "KickMember")
override suspend fun kickMember(request: KickMemberRequest): KickMemberResponse { override suspend fun kickMember(request: KickMemberRequest): KickMemberResponse {
if (!GroupHelper.isAdmin(request.groupId.toString())) { if (!GroupHelper.isAdmin(request.groupId.toString())) {
throw StatusRuntimeException(Status.PERMISSION_DENIED throw StatusRuntimeException(
Status.PERMISSION_DENIED
.withDescription("You are not admin of this group") .withDescription("You are not admin of this group")
) )
} }
GroupHelper.kickMember(request.groupId, request.rejectAddRequest, if (request.hasKickReason()) request.kickReason else "", when(request.targetCase!!) { GroupHelper.kickMember(
request.groupId,
request.rejectAddRequest,
if (request.hasKickReason()) request.kickReason else "",
when (request.targetCase!!) {
KickMemberRequest.TargetCase.TARGET_UIN -> request.targetUin KickMemberRequest.TargetCase.TARGET_UIN -> request.targetUin
KickMemberRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong() KickMemberRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
else -> throw StatusRuntimeException(Status.INVALID_ARGUMENT else -> throw StatusRuntimeException(
Status.INVALID_ARGUMENT
.withDescription("target not set") .withDescription("target not set")
) )
}) }
return kickMemberResponse { } )
return KickMemberResponse.newBuilder().build()
} }
@Grpc("GroupService", "LeaveGroup") @Grpc("GroupService", "LeaveGroup")
override suspend fun leaveGroup(request: LeaveGroupRequest): LeaveGroupResponse { override suspend fun leaveGroup(request: LeaveGroupRequest): LeaveGroupResponse {
GroupHelper.resignTroop(request.groupId.toString()) GroupHelper.resignTroop(request.groupId.toString())
return leaveGroupResponse { } return LeaveGroupResponse.newBuilder().build()
} }
@Grpc("GroupService", "ModifyMemberCard") @Grpc("GroupService", "ModifyMemberCard")
override suspend fun modifyMemberCard(request: ModifyMemberCardRequest): ModifyMemberCardResponse { override suspend fun modifyMemberCard(request: ModifyMemberCardRequest): ModifyMemberCardResponse {
if (!GroupHelper.isAdmin(request.groupId.toString())) { if (!GroupHelper.isAdmin(request.groupId.toString())) {
throw StatusRuntimeException(Status.PERMISSION_DENIED throw StatusRuntimeException(
Status.PERMISSION_DENIED
.withDescription("You are not admin of this group") .withDescription("You are not admin of this group")
) )
} }
GroupHelper.modifyGroupMemberCard(request.groupId, when(request.targetCase!!) { GroupHelper.modifyGroupMemberCard(
request.groupId, when (request.targetCase!!) {
ModifyMemberCardRequest.TargetCase.TARGET_UIN -> request.targetUin ModifyMemberCardRequest.TargetCase.TARGET_UIN -> request.targetUin
ModifyMemberCardRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong() ModifyMemberCardRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid)
else -> throw StatusRuntimeException(Status.INVALID_ARGUMENT .toLong()
else -> throw StatusRuntimeException(
Status.INVALID_ARGUMENT
.withDescription("target not set") .withDescription("target not set")
) )
}, request.card) }, request.card
return modifyMemberCardResponse { } )
return ModifyMemberCardResponse.newBuilder().build()
} }
@Grpc("GroupService", "ModifyGroupName") @Grpc("GroupService", "ModifyGroupName")
override suspend fun modifyGroupName(request: ModifyGroupNameRequest): ModifyGroupNameResponse { override suspend fun modifyGroupName(request: ModifyGroupNameRequest): ModifyGroupNameResponse {
if (!GroupHelper.isAdmin(request.groupId.toString())) { if (!GroupHelper.isAdmin(request.groupId.toString())) {
throw StatusRuntimeException(Status.PERMISSION_DENIED throw StatusRuntimeException(
Status.PERMISSION_DENIED
.withDescription("You are not admin of this group") .withDescription("You are not admin of this group")
) )
} }
GroupHelper.modifyTroopName(request.groupId.toString(), request.groupName) GroupHelper.modifyTroopName(request.groupId.toString(), request.groupName)
return modifyGroupNameResponse { } return ModifyGroupNameResponse.newBuilder().build()
} }
@Grpc("GroupService", "ModifyGroupRemark") @Grpc("GroupService", "ModifyGroupRemark")
override suspend fun modifyGroupRemark(request: ModifyGroupRemarkRequest): ModifyGroupRemarkResponse { override suspend fun modifyGroupRemark(request: ModifyGroupRemarkRequest): ModifyGroupRemarkResponse {
GroupHelper.modifyGroupRemark(request.groupId, request.remark) GroupHelper.modifyGroupRemark(request.groupId, request.remark)
return modifyGroupRemarkResponse { } return ModifyGroupRemarkResponse.newBuilder().build()
} }
@Grpc("GroupService", "SetGroupAdmin") @Grpc("GroupService", "SetGroupAdmin")
override suspend fun setGroupAdmin(request: SetGroupAdminRequest): SetGroupAdminResponse { override suspend fun setGroupAdmin(request: SetGroupAdminRequest): SetGroupAdminResponse {
if (!GroupHelper.isOwner(request.groupId.toString())) { if (!GroupHelper.isOwner(request.groupId.toString())) {
throw StatusRuntimeException(Status.PERMISSION_DENIED throw StatusRuntimeException(
Status.PERMISSION_DENIED
.withDescription("You are not admin of this group") .withDescription("You are not admin of this group")
) )
} }
GroupHelper.setGroupAdmin(request.groupId, when(request.targetCase!!) { GroupHelper.setGroupAdmin(
request.groupId, when (request.targetCase!!) {
SetGroupAdminRequest.TargetCase.TARGET_UIN -> request.targetUin SetGroupAdminRequest.TargetCase.TARGET_UIN -> request.targetUin
SetGroupAdminRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong() SetGroupAdminRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
else -> throw StatusRuntimeException(Status.INVALID_ARGUMENT else -> throw StatusRuntimeException(
Status.INVALID_ARGUMENT
.withDescription("target not set") .withDescription("target not set")
) )
}, request.isAdmin) }, request.isAdmin
)
return setGroupAdminResponse { } return SetGroupAdminResponse.newBuilder().build()
} }
@Grpc("GroupService", "SetGroupUniqueTitle") @Grpc("GroupService", "SetGroupUniqueTitle")
override suspend fun setGroupUniqueTitle(request: SetGroupUniqueTitleRequest): SetGroupUniqueTitleResponse { override suspend fun setGroupUniqueTitle(request: SetGroupUniqueTitleRequest): SetGroupUniqueTitleResponse {
if (!GroupHelper.isAdmin(request.groupId.toString())) { if (!GroupHelper.isAdmin(request.groupId.toString())) {
throw StatusRuntimeException(Status.PERMISSION_DENIED throw StatusRuntimeException(
Status.PERMISSION_DENIED
.withDescription("You are not admin of this group") .withDescription("You are not admin of this group")
) )
} }
GroupHelper.setGroupUniqueTitle(request.groupId, when(request.targetCase!!) { GroupHelper.setGroupUniqueTitle(
request.groupId.toString(), when (request.targetCase!!) {
SetGroupUniqueTitleRequest.TargetCase.TARGET_UIN -> request.targetUin SetGroupUniqueTitleRequest.TargetCase.TARGET_UIN -> request.targetUin
SetGroupUniqueTitleRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong() SetGroupUniqueTitleRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid)
else -> throw StatusRuntimeException(Status.INVALID_ARGUMENT .toLong()
else -> throw StatusRuntimeException(
Status.INVALID_ARGUMENT
.withDescription("target not set") .withDescription("target not set")
) )
}, request.uniqueTitle) }.toString(), request.uniqueTitle
)
return setGroupUniqueTitleResponse { } return SetGroupUniqueTitleResponse.newBuilder().build()
} }
@Grpc("GroupService", "SetGroupWholeBan") @Grpc("GroupService", "SetGroupWholeBan")
override suspend fun setGroupWholeBan(request: SetGroupWholeBanRequest): SetGroupWholeBanResponse { override suspend fun setGroupWholeBan(request: SetGroupWholeBanRequest): SetGroupWholeBanResponse {
if (!GroupHelper.isAdmin(request.groupId.toString())) { if (!GroupHelper.isAdmin(request.groupId.toString())) {
throw StatusRuntimeException(Status.PERMISSION_DENIED throw StatusRuntimeException(
Status.PERMISSION_DENIED
.withDescription("You are not admin of this group") .withDescription("You are not admin of this group")
) )
} }
GroupHelper.setGroupWholeBan(request.groupId, request.isBan) GroupHelper.setGroupWholeBan(request.groupId, request.isBan)
return setGroupWholeBanResponse { } return SetGroupWholeBanResponse.newBuilder().build()
} }
@Grpc("GroupService", "GetGroupInfo") @Grpc("GroupService", "GetGroupInfo")
@ -216,18 +191,20 @@ internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase()
val groupInfo = GroupHelper.getGroupInfo(request.groupId.toString(), true).onFailure { val groupInfo = GroupHelper.getGroupInfo(request.groupId.toString(), true).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get group info").withCause(it)) throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get group info").withCause(it))
}.getOrThrow() }.getOrThrow()
return getGroupInfoResponse { return GetGroupInfoResponse.newBuilder().apply {
this.groupInfo = io.kritor.group.groupInfo { this.groupInfo = GroupInfo.newBuilder().apply {
groupId = groupInfo.troopcode.toLong() groupId = groupInfo.troopcode.toLong()
groupName = groupInfo.troopname.ifNullOrEmpty { groupInfo.troopRemark }.ifNullOrEmpty { groupInfo.newTroopName } ?: "" groupName =
groupInfo.troopname.ifNullOrEmpty { groupInfo.troopRemark }.ifNullOrEmpty { groupInfo.newTroopName }
?: ""
groupRemark = groupInfo.troopRemark ?: "" groupRemark = groupInfo.troopRemark ?: ""
owner = groupInfo.troopowneruin?.toLong() ?: 0 owner = groupInfo.troopowneruin?.toLong() ?: 0
admins.addAll(GroupHelper.getAdminList(groupId)) addAllAdmins(GroupHelper.getAdminList(groupId))
maxMemberCount = groupInfo.wMemberMax maxMemberCount = groupInfo.wMemberMax
memberCount = groupInfo.wMemberNum memberCount = groupInfo.wMemberNum
groupUin = groupInfo.troopuin?.toLong() ?: 0 groupUin = groupInfo.troopuin?.toLong() ?: 0
} }.build()
} }.build()
} }
@Grpc("GroupService", "GetGroupList") @Grpc("GroupService", "GetGroupList")
@ -235,36 +212,46 @@ internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase()
val groupList = GroupHelper.getGroupList(if (request.hasRefresh()) request.refresh else false).onFailure { val groupList = GroupHelper.getGroupList(if (request.hasRefresh()) request.refresh else false).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get group list").withCause(it)) throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get group list").withCause(it))
}.getOrThrow() }.getOrThrow()
return getGroupListResponse { return GetGroupListResponse.newBuilder().apply {
groupList.forEach { groupInfo -> groupList.forEach { groupInfo ->
this.groupInfo.add(io.kritor.group.groupInfo { this.addGroupsInfo(GroupInfo.newBuilder().apply {
groupId = groupInfo.troopcode.toLong() groupId = groupInfo.troopcode.ifNullOrEmpty { groupInfo.uin }.ifNullOrEmpty { groupInfo.troopuin }?.toLong() ?: 0
groupName = groupInfo.troopname.ifNullOrEmpty { groupInfo.troopRemark }.ifNullOrEmpty { groupInfo.newTroopName } ?: "" groupName = groupInfo.troopname.ifNullOrEmpty { groupInfo.troopRemark }
.ifNullOrEmpty { groupInfo.newTroopName }
?: ""
groupRemark = groupInfo.troopRemark ?: "" groupRemark = groupInfo.troopRemark ?: ""
owner = groupInfo.troopowneruin?.toLong() ?: 0 owner = groupInfo.troopowneruin?.toLong() ?: 0
admins.addAll(GroupHelper.getAdminList(groupId)) addAllAdmins(GroupHelper.getAdminList(groupId))
maxMemberCount = groupInfo.wMemberMax maxMemberCount = groupInfo.wMemberMax
memberCount = groupInfo.wMemberNum memberCount = groupInfo.wMemberNum
groupUin = groupInfo.troopuin?.toLong() ?: 0 groupUin = groupInfo.troopuin?.toLong() ?: 0
}) })
} }
} }.build()
} }
@Grpc("GroupService", "GetGroupMemberInfo") @Grpc("GroupService", "GetGroupMemberInfo")
override suspend fun getGroupMemberInfo(request: GetGroupMemberInfoRequest): GetGroupMemberInfoResponse { override suspend fun getGroupMemberInfo(request: GetGroupMemberInfoRequest): GetGroupMemberInfoResponse {
val memberInfo = GroupHelper.getTroopMemberInfoByUin(request.groupId, when(request.targetCase!!) { val memberInfo = GroupHelper.getTroopMemberInfoByUin(
GetGroupMemberInfoRequest.TargetCase.UIN -> request.uin request.groupId.toString(), when (request.targetCase!!) {
GetGroupMemberInfoRequest.TargetCase.UID -> ContactHelper.getUinByUidAsync(request.uid).toLong() GetGroupMemberInfoRequest.TargetCase.TARGET_UID -> request.targetUin
else -> throw StatusRuntimeException(Status.INVALID_ARGUMENT GetGroupMemberInfoRequest.TargetCase.TARGET_UIN -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
else -> throw StatusRuntimeException(
Status.INVALID_ARGUMENT
.withDescription("target not set") .withDescription("target not set")
) )
}).onFailure { }.toString()
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get group member info").withCause(it)) ).onFailure {
throw StatusRuntimeException(
Status.INTERNAL.withDescription("unable to get group member info").withCause(it)
)
}.getOrThrow() }.getOrThrow()
return getGroupMemberInfoResponse { return GetGroupMemberInfoResponse.newBuilder().apply {
groupMemberInfo = groupMemberInfo { groupMemberInfo = GroupMemberInfo.newBuilder().apply {
uid = if (request.targetCase == GetGroupMemberInfoRequest.TargetCase.UID) request.uid else ContactHelper.getUidByUinAsync(request.uin) uid =
if (request.targetCase == GetGroupMemberInfoRequest.TargetCase.TARGET_UID) request.targetUid else ContactHelper.getUidByUinAsync(
request.targetUin
)
uin = memberInfo.memberuin?.toLong() ?: 0 uin = memberInfo.memberuin?.toLong() ?: 0
nick = memberInfo.troopnick nick = memberInfo.troopnick
.ifNullOrEmpty { memberInfo.hwName } .ifNullOrEmpty { memberInfo.hwName }
@ -272,73 +259,78 @@ internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase()
.ifNullOrEmpty { memberInfo.friendnick } ?: "" .ifNullOrEmpty { memberInfo.friendnick } ?: ""
age = memberInfo.age.toInt() age = memberInfo.age.toInt()
uniqueTitle = memberInfo.mUniqueTitle ?: "" uniqueTitle = memberInfo.mUniqueTitle ?: ""
uniqueTitleExpireTime = memberInfo.mUniqueTitleExpire uniqueTitleExpireTime = memberInfo.mUniqueTitleExpire.toLong()
card = memberInfo.troopnick.ifNullOrEmpty { memberInfo.friendnick } ?: "" card = memberInfo.troopnick.ifNullOrEmpty { memberInfo.friendnick } ?: ""
joinTime = memberInfo.join_time joinTime = memberInfo.join_time
lastActiveTime = memberInfo.last_active_time lastActiveTime = memberInfo.last_active_time
level = memberInfo.level level = memberInfo.level
shutUpTimestamp = memberInfo.gagTimeStamp shutUpTime = memberInfo.gagTimeStamp
distance = memberInfo.distance distance = memberInfo.distance
honor.addAll((memberInfo.honorList ?: "") addAllHonors((memberInfo.honorList ?: "")
.split("|") .split("|")
.filter { it.isNotBlank() } .filter { it.isNotBlank() }
.map { it.toInt() }) .map { it.toInt() })
unfriendly = false unfriendly = false
cardChangeable = GroupHelper.isAdmin(request.groupId.toString()) cardChangeable = GroupHelper.isAdmin(request.groupId.toString())
} }.build()
} }.build()
} }
@Grpc("GroupService", "GetGroupMemberList") @Grpc("GroupService", "GetGroupMemberList")
override suspend fun getGroupMemberList(request: GetGroupMemberListRequest): GetGroupMemberListResponse { override suspend fun getGroupMemberList(request: GetGroupMemberListRequest): GetGroupMemberListResponse {
val memberList = GroupHelper.getGroupMemberList(request.groupId.toString(), if (request.hasRefresh()) request.refresh else false).onFailure { val memberList = GroupHelper.getGroupMemberList(
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get group member list").withCause(it)) request.groupId,
if (request.hasRefresh()) request.refresh else false
).onFailure {
throw StatusRuntimeException(
Status.INTERNAL.withDescription("unable to get group member list").withCause(it)
)
}.getOrThrow() }.getOrThrow()
return getGroupMemberListResponse { return GetGroupMemberListResponse.newBuilder().apply {
memberList.forEach { memberInfo -> memberList.values.forEach { memberInfo ->
this.groupMemberInfo.add(groupMemberInfo { this.addGroupMembersInfo(GroupMemberInfo.newBuilder().apply {
uid = ContactHelper.getUidByUinAsync(memberInfo.memberuin?.toLong() ?: 0) uid = memberInfo.uid
uin = memberInfo.memberuin?.toLong() ?: 0 uin = memberInfo.uin
nick = memberInfo.troopnick nick = memberInfo.nick ?: ""
.ifNullOrEmpty { memberInfo.hwName } age = 0
.ifNullOrEmpty { memberInfo.troopColorNick } uniqueTitle = memberInfo.memberSpecialTitle ?: ""
.ifNullOrEmpty { memberInfo.friendnick } ?: "" uniqueTitleExpireTime = memberInfo.specialTitleExpireTime
age = memberInfo.age.toInt() card = memberInfo.cardName.ifNullOrEmpty { memberInfo.nick } ?: ""
uniqueTitle = memberInfo.mUniqueTitle ?: "" joinTime = memberInfo.joinTime.toLong()
uniqueTitleExpireTime = memberInfo.mUniqueTitleExpire lastActiveTime = memberInfo.lastSpeakTime.toLong()
card = memberInfo.troopnick.ifNullOrEmpty { memberInfo.friendnick } ?: "" level = memberInfo.memberLevel
joinTime = memberInfo.join_time shutUpTime = memberInfo.shutUpTime.toLong()
lastActiveTime = memberInfo.last_active_time
level = memberInfo.level
shutUpTimestamp = memberInfo.gagTimeStamp
distance = memberInfo.distance distance = 0
honor.addAll((memberInfo.honorList ?: "") addAllHonors(memberInfo.groupHonor.let { bytes ->
.split("|") val honor = troop_honor.GroupUserCardHonor()
.filter { it.isNotBlank() } honor.mergeFrom(bytes)
.map { it.toInt() }) honor.id.get()
})
unfriendly = false unfriendly = false
cardChangeable = GroupHelper.isAdmin(request.groupId.toString()) cardChangeable = memberInfo.role == com.tencent.qqnt.kernelpublic.nativeinterface.MemberRole.ADMIN
}) })
} }
} }.build()
} }
@Grpc("GroupService", "GetProhibitedUserList") @Grpc("GroupService", "GetProhibitedUserList")
override suspend fun getProhibitedUserList(request: GetProhibitedUserListRequest): GetProhibitedUserListResponse { override suspend fun getProhibitedUserList(request: GetProhibitedUserListRequest): GetProhibitedUserListResponse {
val prohibitedList = GroupHelper.getProhibitedMemberList(request.groupId).onFailure { val prohibitedList = GroupHelper.getProhibitedMemberList(request.groupId).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get prohibited user list").withCause(it)) throw StatusRuntimeException(
Status.INTERNAL.withDescription("unable to get prohibited user list").withCause(it)
)
}.getOrThrow() }.getOrThrow()
return getProhibitedUserListResponse { return GetProhibitedUserListResponse.newBuilder().apply {
prohibitedList.forEach { prohibitedList.forEach {
this.prohibitedUserInfo.add(prohibitedUserInfo { this.addProhibitedUsersInfo(ProhibitedUserInfo.newBuilder().apply {
uid = ContactHelper.getUidByUinAsync(it.memberUin) uid = ContactHelper.getUidByUinAsync(it.memberUin)
uin = it.memberUin uin = it.memberUin
prohibitedTime = it.shutuptimestap prohibitedTime = it.shutuptimestap.toLong()
}) })
} }
} }.build()
} }
@Grpc("GroupService", "GetRemainCountAtAll") @Grpc("GroupService", "GetRemainCountAtAll")
@ -346,52 +338,57 @@ internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase()
val remainAtAllRsp = GroupHelper.getGroupRemainAtAllRemain(request.groupId).onFailure { val remainAtAllRsp = GroupHelper.getGroupRemainAtAllRemain(request.groupId).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get remain count").withCause(it)) throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get remain count").withCause(it))
}.getOrThrow() }.getOrThrow()
return getRemainCountAtAllResponse { return GetRemainCountAtAllResponse.newBuilder().apply {
accessAtAll = remainAtAllRsp.bool_can_at_all.get() accessAtAll = remainAtAllRsp.bool_can_at_all.get()
remainCountForGroup = remainAtAllRsp.uint32_remain_at_all_count_for_group.get() remainCountForGroup = remainAtAllRsp.uint32_remain_at_all_count_for_group.get()
remainCountForSelf = remainAtAllRsp.uint32_remain_at_all_count_for_uin.get() remainCountForSelf = remainAtAllRsp.uint32_remain_at_all_count_for_uin.get()
} }.build()
} }
@Grpc("GroupService", "GetNotJoinedGroupInfo") @Grpc("GroupService", "GetNotJoinedGroupInfo")
override suspend fun getNotJoinedGroupInfo(request: GetNotJoinedGroupInfoRequest): GetNotJoinedGroupInfoResponse { override suspend fun getNotJoinedGroupInfo(request: GetNotJoinedGroupInfoRequest): GetNotJoinedGroupInfoResponse {
val groupInfo = GroupHelper.getNotJoinedGroupInfo(request.groupId).onFailure { val groupInfo = GroupHelper.getNotJoinedGroupInfo(request.groupId).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get not joined group info").withCause(it)) throw StatusRuntimeException(
Status.INTERNAL.withDescription("unable to get not joined group info").withCause(it)
)
}.getOrThrow() }.getOrThrow()
return getNotJoinedGroupInfoResponse { return GetNotJoinedGroupInfoResponse.newBuilder().apply {
this.groupInfo = notJoinedGroupInfo { this.groupInfo = NotJoinedGroupInfo.newBuilder().apply {
groupId = groupInfo.groupId groupId = groupInfo.groupId
groupName = groupInfo.groupName groupName = groupInfo.groupName
owner = groupInfo.owner owner = groupInfo.owner
maxMemberCount = groupInfo.maxMember maxMemberCount = groupInfo.maxMember
memberCount = groupInfo.memberCount memberCount = groupInfo.memberCount
groupDesc = groupInfo.groupDesc groupDesc = groupInfo.groupDesc
createTime = groupInfo.createTime.toInt() createTime = groupInfo.createTime
groupFlag = groupInfo.groupFlag groupFlag = groupInfo.groupFlag
groupFlagExt = groupInfo.groupFlagExt groupFlagExt = groupInfo.groupFlagExt
} }.build()
} }.build()
} }
@Grpc("GroupService", "GetGroupHonor") @Grpc("GroupService", "GetGroupHonor")
override suspend fun getGroupHonor(request: GetGroupHonorRequest): GetGroupHonorResponse { override suspend fun getGroupHonor(request: GetGroupHonorRequest): GetGroupHonorResponse {
return getGroupHonorResponse { return GetGroupHonorResponse.newBuilder().apply {
GroupHelper.getGroupMemberList(request.groupId.toString(), true).onFailure { GroupHelper.getGroupMemberList(request.groupId, true).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get group member list").withCause(it)) throw StatusRuntimeException(
Status.INTERNAL.withDescription("unable to get group member list").withCause(it)
)
}.onSuccess { memberList -> }.onSuccess { memberList ->
memberList.forEach { member -> memberList.values.forEach { member ->
(member.honorList ?: "").split("|") member.groupHonor.let { bytes ->
.filter { it.isNotBlank() } val honor = troop_honor.GroupUserCardHonor()
.map { it.toInt() }.forEach { honor.mergeFrom(bytes)
val honor = decodeHonor(member.memberuin.toLong(), it, member.mHonorRichFlag) honor.id.get()
}.forEach {
val honor = decodeHonor(member.uin, it, 0)
if (honor != null) { if (honor != null) {
groupHonorInfo.add(groupHonorInfo { addGroupHonorsInfo(GroupHonorInfo.newBuilder().apply {
uid = ContactHelper.getUidByUinAsync(member.memberuin.toLong()) uid = member.uid
uin = member.memberuin.toLong() uin = member.uin
nick = member.troopnick nick = member.nick.ifEmpty {
.ifNullOrEmpty { member.hwName } member.cardName
.ifNullOrEmpty { member.troopColorNick } } ?: ""
.ifNullOrEmpty { member.friendnick } ?: ""
honorName = honor.honorName honorName = honor.honorName
avatar = honor.honorIconUrl avatar = honor.honorIconUrl
id = honor.honorId id = honor.honorId
@ -401,6 +398,6 @@ internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase()
} }
} }
} }
} }.build()
} }
} }

View File

@ -1,7 +1,475 @@
package kritor.service package kritor.service
import io.kritor.message.MessageServiceGrpcKt import com.tencent.mobileqq.qroute.QRoute
import com.tencent.qqnt.kernelpublic.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.common.*
import io.kritor.message.*
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import protobuf.auto.toByteArray
import protobuf.message.*
import protobuf.message.element.GeneralFlags
import protobuf.message.routing.C2C
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 kotlin.coroutines.resume
import kotlin.random.Random
import kotlin.random.nextUInt
internal object MessageService: MessageServiceGrpcKt.MessageServiceCoroutineImplBase() { internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImplBase() {
@Grpc("MessageService", "SendMessage")
override suspend fun sendMessage(request: SendMessageRequest): SendMessageResponse {
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 uniseq = MessageHelper.generateMsgId(contact.chatType)
return SendMessageResponse.newBuilder().apply {
this.messageId = MessageHelper.sendMessage(
contact,
NtMsgConvertor.convertToNtMsgs(contact, uniseq, request.elementsList),
request.retryCount,
uniseq
).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withCause(it))
}.getOrThrow().toString()
}.build()
}
@Grpc("MessageService", "SendMessageByResId")
override suspend fun sendMessageByResId(request: SendMessageByResIdRequest): SendMessageByResIdResponse {
val contact = request.contact
val req = PbSendMsgReq(
routingHead = when (request.contact.scene) {
Scene.GROUP -> RoutingHead(grp = Grp(contact.longPeer().toUInt()))
Scene.FRIEND -> RoutingHead(c2c = C2C(contact.longPeer().toUInt()))
else -> RoutingHead(grp = Grp(contact.longPeer().toUInt()))
},
contentHead = ContentHead(1, 0, 0, 0),
msgBody = MsgBody(
richText = RichText(
elements = arrayListOf(
Elem(
generalFlags = GeneralFlags(
longTextFlag = 1u,
longTextResid = request.resId
)
)
)
)
),
msgSeq = Random.nextUInt(),
msgRand = Random.nextUInt(),
msgVia = 0u
)
QQInterfaces.sendBuffer("MessageSvc.PbSendMsg", true, req.toByteArray())
return SendMessageByResIdResponse.newBuilder().build()
}
@Grpc("MessageService", "SetMessageReaded")
override suspend fun setMessageReaded(request: SetMessageReadRequest): SetMessageReadResponse {
val contact = request.contact
val kernelService = NTServiceFetcher.kernelService
val sessionService = kernelService.wrapperSession
val service = sessionService.msgService
val chatType = when (contact.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"))
}
service.clearMsgRecords(
Contact(
chatType,
contact.peer,
contact.subPeer
), null)
return SetMessageReadResponse.newBuilder().build()
}
@Grpc("MessageService", "RecallMessage")
override suspend fun recallMessage(request: RecallMessageRequest): RecallMessageResponse {
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 kernelService = NTServiceFetcher.kernelService
val sessionService = kernelService.wrapperSession
val service = sessionService.msgService
service.recallMsg(contact, arrayListOf(request.messageId.toLong())) { code, msg ->
if (code != 0) {
LogCenter.log("消息撤回失败: $code:$msg", Level.WARN)
}
}
return RecallMessageResponse.newBuilder().build()
}
@Grpc("MessageService", "GetMessage")
override suspend fun getMessage(request: GetMessageRequest): GetMessageResponse {
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 msg: MsgRecord = withTimeoutOrNull(5000) {
val service = QRoute.api(IMsgService::class.java)
suspendCancellableCoroutine { continuation ->
service.getMsgsByMsgId(contact, arrayListOf(request.messageId.toLong())) { code, _, msgRecords ->
if (code == 0 && msgRecords.isNotEmpty()) {
continuation.resume(msgRecords.first())
} else {
continuation.resume(null)
}
}
continuation.invokeOnCancellation {
continuation.resume(null)
}
}
} ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Message not found"))
return GetMessageResponse.newBuilder().apply {
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 ?: ""
}.build()
this.messageSeq = msg.msgSeq
this.addAllElements(msg.elements.toKritorReqMessages(contact))
}.build()
}.build()
}
@Grpc("MessageService", "GetMessageBySeq")
override suspend fun getMessageBySeq(request: GetMessageBySeqRequest): GetMessageBySeqResponse {
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 msg: MsgRecord = withTimeoutOrNull(5000) {
val service = QRoute.api(IMsgService::class.java)
suspendCancellableCoroutine { continuation ->
service.getMsgsBySeqAndCount(contact, request.messageSeq, 1, true) { code, _, msgRecords ->
if (code == 0 && msgRecords.isNotEmpty()) {
continuation.resume(msgRecords.first())
} else {
continuation.resume(null)
}
}
continuation.invokeOnCancellation {
continuation.resume(null)
}
}
} ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Message not found"))
return GetMessageBySeqResponse.newBuilder().apply {
this.message = PushMessageBody.newBuilder().apply {
this.messageId = msg.msgId.toString()
this.contact = request.contact
this.sender = Sender.newBuilder().apply {
this.uin = msg.senderUin
this.nick = msg.sendNickName ?: ""
this.uid = msg.senderUid ?: ""
}.build()
this.messageSeq = msg.msgSeq
this.addAllElements(msg.elements.toKritorReqMessages(contact))
}.build()
}.build()
}
@Grpc("MessageService", "GetHistoryMessage")
override suspend fun getHistoryMessage(request: GetHistoryMessageRequest): GetHistoryMessageResponse {
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 msgs: List<MsgRecord> = withTimeoutOrNull(5000) {
val service = QRoute.api(IMsgService::class.java)
suspendCancellableCoroutine { continuation ->
service.getMsgs(contact, request.startMessageId.toLong(), request.count, true) { code, _, msgRecords ->
if (code == 0 && msgRecords.isNotEmpty()) {
continuation.resume(msgRecords)
} else {
continuation.resume(null)
}
}
continuation.invokeOnCancellation {
continuation.resume(null)
}
}
} ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Messages not found"))
return GetHistoryMessageResponse.newBuilder().apply {
msgs.forEach {
addMessages(PushMessageBody.newBuilder().apply {
this.messageId = it.msgId.toString()
this.contact = request.contact
this.sender = Sender.newBuilder().apply {
this.uin = it.senderUin
this.nick = it.sendNickName ?: ""
this.uid = it.senderUid ?: ""
}.build()
this.messageSeq = it.msgSeq
this.addAllElements(it.elements.toKritorReqMessages(contact))
})
}
}.build()
}
@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.toLong()
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(
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.toLong())) { code, _, msgRecords ->
if (code == 0 && msgRecords.isNotEmpty()) {
continuation.resume(msgRecords.first())
} else {
continuation.resume(null)
}
}
continuation.invokeOnCancellation {
continuation.resume(null)
}
}
} ?: 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 DeleteEssenceMessageResponse.newBuilder().build()
}
@Grpc("MessageService", "GetEssenceMessageList")
override suspend fun getEssenceMessageList(request: GetEssenceMessageListRequest): GetEssenceMessageListResponse {
val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, request.groupId.toString())
return GetEssenceMessageListResponse.newBuilder().apply {
MessageHelper.getEssenceMessageList(request.groupId, request.page, request.pageSize).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withCause(it))
}.getOrThrow().forEach {
addMessages(EssenceMessageBody.newBuilder().apply {
withTimeoutOrNull(5000) {
val service = QRoute.api(IMsgService::class.java)
suspendCancellableCoroutine { continuation ->
service.getMsgsBySeqAndCount(contact, it.messageSeq, 1, true) { code, _, msgRecords ->
if (code == 0 && msgRecords.isNotEmpty()) {
continuation.resume(msgRecords.first())
} else {
continuation.resume(null)
}
}
continuation.invokeOnCancellation {
continuation.resume(null)
}
}
}?.let {
this.messageId = it.msgId.toString()
}
this.messageSeq = it.messageSeq
this.messageTime = it.senderTime
this.senderNick = it.senderNick
this.senderUin = it.senderId
this.operationTime = it.operatorTime
this.operatorNick = it.operatorNick
this.operatorUin = it.operatorId
this.jsonElements = it.messageContent.toString()
})
}
}.build()
}
@Grpc("MessageService", "SetEssenceMessage")
override suspend fun setEssenceMessage(request: SetEssenceMessageRequest): SetEssenceMessageResponse {
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.toLong())) { code, _, msgRecords ->
if (code == 0 && msgRecords.isNotEmpty()) {
continuation.resume(msgRecords.first())
} else {
continuation.resume(null)
}
}
continuation.invokeOnCancellation {
continuation.resume(null)
}
}
} ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Message not found"))
if (MessageHelper.setEssenceMessage(request.groupId, msg.msgSeq, msg.msgRandom) == null) {
throw StatusRuntimeException(Status.NOT_FOUND.withDescription("set essence message failed"))
}
return SetEssenceMessageResponse.newBuilder().build()
}
@Grpc("MessageService", "ReactMessageWithEmoji")
override suspend fun reactMessageWithEmoji(request: ReactMessageWithEmojiRequest): ReactMessageWithEmojiResponse {
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 msg: MsgRecord = withTimeoutOrNull(5000) {
val service = QRoute.api(IMsgService::class.java)
suspendCancellableCoroutine { continuation ->
service.getMsgsByMsgId(contact, arrayListOf(request.messageId.toLong())) { code, _, msgRecords ->
if (code == 0 && msgRecords.isNotEmpty()) {
continuation.resume(msgRecords.first())
} else {
continuation.resume(null)
}
}
continuation.invokeOnCancellation {
continuation.resume(null)
}
}
} ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Message not found"))
MessageHelper.setGroupMessageCommentFace(
request.contact.longPeer(),
msg.msgSeq.toULong(),
request.faceId.toString(),
request.isSet
)
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

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

View File

@ -2,5 +2,5 @@ package moe.fuqiuluo.shamrock.config
object EnableOldBDH: ConfigKey<Boolean>() { object EnableOldBDH: ConfigKey<Boolean>() {
override fun name() = "enable_old_bdh" override fun name() = "enable_old_bdh"
override fun default() = false override fun default() = true
} }

View File

@ -20,6 +20,7 @@ private val configKeys = setOf(
ResourceGroup, ResourceGroup,
RPCAddress, RPCAddress,
RPCPort, RPCPort,
AliveReply,
) )
internal object ShamrockConfig: Properties() { internal object ShamrockConfig: Properties() {

View File

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

View File

@ -4,84 +4,59 @@ package moe.fuqiuluo.shamrock.internals
import com.tencent.qqnt.kernel.nativeinterface.MsgElement import com.tencent.qqnt.kernel.nativeinterface.MsgElement
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
import io.kritor.event.GroupApplyType import io.kritor.event.*
import io.kritor.event.GroupMemberBanType import io.kritor.common.PushMessageBody
import io.kritor.event.GroupMemberDecreasedType import io.kritor.common.Contact
import io.kritor.event.GroupMemberIncreasedType import io.kritor.common.Scene
import io.kritor.event.MessageEvent import io.kritor.common.Sender
import io.kritor.event.NoticeEvent
import io.kritor.event.NoticeType
import io.kritor.event.RequestType
import io.kritor.event.RequestsEvent
import io.kritor.event.Scene
import io.kritor.event.contact
import io.kritor.event.essenceMessageNotice
import io.kritor.event.friendApplyRequest
import io.kritor.event.friendFileComeNotice
import io.kritor.event.friendPokeNotice
import io.kritor.event.friendRecallNotice
import io.kritor.event.groupAdminChangedNotice
import io.kritor.event.groupApplyRequest
import io.kritor.event.groupFileComeNotice
import io.kritor.event.groupMemberBannedNotice
import io.kritor.event.groupMemberDecreasedNotice
import io.kritor.event.groupMemberIncreasedNotice
import io.kritor.event.groupPokeNotice
import io.kritor.event.groupRecallNotice
import io.kritor.event.groupSignNotice
import io.kritor.event.groupUniqueTitleChangedNotice
import io.kritor.event.groupWholeBanNotice
import io.kritor.event.messageEvent
import io.kritor.event.noticeEvent
import io.kritor.event.requestsEvent
import io.kritor.event.sender
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import qq.service.QQInterfaces import qq.service.QQInterfaces
import qq.service.msg.toKritorMessages import qq.service.contact.ContactHelper
import qq.service.msg.toKritorEventMessages
internal object GlobalEventTransmitter: QQInterfaces() { internal object GlobalEventTransmitter : QQInterfaces() {
private val messageEventFlow by lazy { private val MessageEventFlow by lazy {
MutableSharedFlow<Pair<MsgRecord, MessageEvent>>() MutableSharedFlow<Pair<MsgRecord, PushMessageBody>>()
} }
private val noticeEventFlow by lazy { private val noticeEventFlow by lazy {
MutableSharedFlow<NoticeEvent>() MutableSharedFlow<NoticeEvent>()
} }
private val requestEventFlow by lazy { private val requestEventFlow by lazy {
MutableSharedFlow<RequestsEvent>() MutableSharedFlow<RequestEvent>()
} }
private suspend fun pushNotice(noticeEvent: NoticeEvent) = noticeEventFlow.emit(noticeEvent) 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) = messageEventFlow.emit(record to message) private suspend fun transMessageEvent(record: MsgRecord, message: PushMessageBody) =
MessageEventFlow.emit(record to message)
object MessageTransmitter { object MessageTransmitter {
suspend fun transGroupMessage( suspend fun transGroupMessage(
record: MsgRecord, record: MsgRecord,
elements: ArrayList<MsgElement>, elements: ArrayList<MsgElement>,
): Boolean { ): Boolean {
transMessageEvent(record, messageEvent { transMessageEvent(record, PushMessageBody.newBuilder().apply {
this.time = record.msgTime.toInt() this.time = record.msgTime
this.scene = Scene.GROUP this.messageId = record.msgId.toString()
this.messageId = record.msgId
this.messageSeq = record.msgSeq this.messageSeq = record.msgSeq
this.contact = contact { this.contact = Contact.newBuilder().apply {
this.scene = scene this.scene = Scene.GROUP
this.peer = record.peerUin.toString() this.peer = record.peerUin.toString()
this.subPeer = record.peerUid this.subPeer = record.peerUid
} }.build()
this.sender = sender { this.sender = Sender.newBuilder().apply {
this.uin = record.senderUin this.uin = record.senderUin
this.uid = record.senderUid this.uid = record.senderUid
this.nick = record.sendNickName this.nick = record.sendNickName
} }.build()
this.elements.addAll(elements.toKritorMessages(record)) this.addAllElements(elements.toKritorEventMessages(record))
}) }.build())
return true return true
} }
@ -89,23 +64,22 @@ internal object GlobalEventTransmitter: QQInterfaces() {
record: MsgRecord, record: MsgRecord,
elements: ArrayList<MsgElement>, elements: ArrayList<MsgElement>,
): Boolean { ): Boolean {
transMessageEvent(record, messageEvent { transMessageEvent(record, PushMessageBody.newBuilder().apply {
this.time = record.msgTime.toInt() this.time = record.msgTime
this.scene = Scene.FRIEND this.messageId = record.msgId.toString()
this.messageId = record.msgId
this.messageSeq = record.msgSeq this.messageSeq = record.msgSeq
this.contact = contact { this.contact = Contact.newBuilder().apply {
this.scene = scene this.scene = Scene.FRIEND
this.peer = record.senderUin.toString() this.peer = record.senderUid
this.subPeer = record.senderUid this.subPeer = record.senderUid
} }.build()
this.sender = sender { this.sender = Sender.newBuilder().apply {
this.uin = record.senderUin this.uin = record.senderUin
this.uid = record.senderUid this.uid = record.senderUid
this.nick = record.sendNickName this.nick = record.sendNickName
} }.build()
this.elements.addAll(elements.toKritorMessages(record)) this.addAllElements(elements.toKritorEventMessages(record))
}) }.build())
return true return true
} }
@ -115,23 +89,22 @@ internal object GlobalEventTransmitter: QQInterfaces() {
groupCode: Long, groupCode: Long,
fromNick: String, fromNick: String,
): Boolean { ): Boolean {
transMessageEvent(record, messageEvent { transMessageEvent(record, PushMessageBody.newBuilder().apply {
this.time = record.msgTime.toInt() this.time = record.msgTime
this.scene = Scene.FRIEND this.messageId = record.msgId.toString()
this.messageId = record.msgId
this.messageSeq = record.msgSeq this.messageSeq = record.msgSeq
this.contact = contact { this.contact = Contact.newBuilder().apply {
this.scene = scene this.scene = if (groupCode > 0) Scene.STRANGER_FROM_GROUP else Scene.STRANGER
this.peer = record.senderUin.toString() this.peer = record.senderUid
this.subPeer = groupCode.toString() this.subPeer = groupCode.toString()
} }.build()
this.sender = sender { this.sender = Sender.newBuilder().apply {
this.uin = record.senderUin this.uin = record.senderUin
this.uid = record.senderUid this.uid = record.senderUid
this.nick = record.sendNickName this.nick = record.sendNickName
} }.build()
this.elements.addAll(elements.toKritorMessages(record)) this.addAllElements(elements.toKritorEventMessages(record))
}) }.build())
return true return true
} }
@ -139,23 +112,22 @@ internal object GlobalEventTransmitter: QQInterfaces() {
record: MsgRecord, record: MsgRecord,
elements: ArrayList<MsgElement>, elements: ArrayList<MsgElement>,
): Boolean { ): Boolean {
transMessageEvent(record, messageEvent { transMessageEvent(record, PushMessageBody.newBuilder().apply {
this.time = record.msgTime.toInt() this.time = record.msgTime
this.scene = Scene.GUILD this.messageId = record.msgId.toString()
this.messageId = record.msgId
this.messageSeq = record.msgSeq this.messageSeq = record.msgSeq
this.contact = contact { this.contact = Contact.newBuilder().apply {
this.scene = scene this.scene = Scene.GUILD
this.peer = record.channelId.toString() this.peer = record.guildId ?: ""
this.subPeer = record.guildId this.subPeer = record.channelId ?: ""
} }.build()
this.sender = sender { this.sender = Sender.newBuilder().apply {
this.uin = record.senderUin this.uin = record.senderUin
this.uid = record.senderUid this.uid = record.senderUid
this.nick = record.sendNickName this.nick = record.sendNickName
} }.build()
this.elements.addAll(elements.toKritorMessages(record)) this.addAllElements(elements.toKritorEventMessages(record))
}) }.build())
return true return true
} }
} }
@ -169,7 +141,8 @@ internal object GlobalEventTransmitter: QQInterfaces() {
*/ */
suspend fun transPrivateFileEvent( suspend fun transPrivateFileEvent(
msgTime: Long, msgTime: Long,
userId: Long, senderUid: String,
senderUin: Long,
fileId: String, fileId: String,
fileSubId: String, fileSubId: String,
fileName: String, fileName: String,
@ -177,19 +150,20 @@ internal object GlobalEventTransmitter: QQInterfaces() {
expireTime: Long, expireTime: Long,
url: String url: String
): Boolean { ): Boolean {
pushNotice(noticeEvent { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.FRIEND_FILE_COME this.type = NoticeEvent.NoticeType.PRIVATE_FILE_UPLOADED
this.time = msgTime.toInt() this.time = msgTime
this.friendFileCome = friendFileComeNotice { this.privateFileUploaded = PrivateFileUploadedNotice.newBuilder().apply {
this.fileId = fileId this.fileId = fileId
this.fileName = fileName this.fileName = fileName
this.operator = userId this.operatorUid = senderUid
this.operatorUin = senderUin
this.fileSize = fileSize this.fileSize = fileSize
this.expireTime = expireTime.toInt() this.expireTime = expireTime
this.fileSubId = fileSubId this.fileSubId = fileSubId.toInt() // todo(这玩意真的是一个数字?)
this.url = url this.fileUrl = url
} }.build()
}) }.build())
return true return true
} }
@ -198,7 +172,8 @@ internal object GlobalEventTransmitter: QQInterfaces() {
*/ */
suspend fun transGroupFileEvent( suspend fun transGroupFileEvent(
msgTime: Long, msgTime: Long,
userId: Long, senderUid: String,
senderUin: Long,
groupId: Long, groupId: Long,
uuid: String, uuid: String,
fileName: String, fileName: String,
@ -206,19 +181,20 @@ internal object GlobalEventTransmitter: QQInterfaces() {
bizId: Int, bizId: Int,
url: String url: String
): Boolean { ): Boolean {
pushNotice(noticeEvent { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_FILE_COME this.type = NoticeEvent.NoticeType.GROUP_FILE_UPLOADED
this.time = msgTime.toInt() this.time = msgTime
this.groupFileCome = groupFileComeNotice { this.groupFileUploaded = GroupFileUploadedNotice.newBuilder().apply {
this.groupId = groupId this.groupId = groupId
this.operator = userId this.operatorUid = senderUid
this.operatorUin = senderUin
this.fileId = uuid this.fileId = uuid
this.fileName = fileName this.fileName = fileName
this.fileSize = fileSize this.fileSize = fileSize
this.biz = bizId this.fileSubId = bizId
this.url = url this.fileUrl = url
} }.build()
}) }.build())
return true return true
} }
} }
@ -227,33 +203,50 @@ internal object GlobalEventTransmitter: QQInterfaces() {
* 群聊通知 通知器 * 群聊通知 通知器
*/ */
object GroupNoticeTransmitter { object GroupNoticeTransmitter {
suspend fun transGroupSign(time: Long, target: Long, action: String?, rankImg: String?, groupCode: Long): Boolean { suspend fun transGroupSign(
pushNotice(noticeEvent { time: Long,
this.type = NoticeType.GROUP_SIGN target: Long,
this.time = time.toInt() action: String,
this.groupSign = groupSignNotice { rankImg: String,
groupCode: Long
): Boolean {
pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeEvent.NoticeType.GROUP_SIGN_IN
this.time = time
this.groupSignIn = GroupSignInNotice.newBuilder().apply {
this.groupId = groupCode this.groupId = groupCode
this.targetUid = ContactHelper.getUidByUinAsync(target)
this.targetUin = target this.targetUin = target
this.action = action ?: "" this.action = action
this.suffix = "" this.rankImage = rankImg
this.rankImage = rankImg ?: "" }.build()
} }.build())
})
return true return true
} }
suspend fun transGroupPoke(time: Long, operator: Long, target: Long, action: String?, suffix: String?, actionImg: String?, groupCode: Long): Boolean { suspend fun transGroupPoke(
pushNotice(noticeEvent { time: Long,
this.type = NoticeType.GROUP_POKE operator: Long,
this.time = time.toInt() target: Long,
this.groupPoke = groupPokeNotice { action: String,
this.action = action ?: "" suffix: String,
this.target = target actionImg: String,
this.operator = operator groupCode: Long
this.suffix = suffix ?: "" ): Boolean {
this.actionImage = actionImg ?: "" pushNotice(NoticeEvent.newBuilder().apply {
} this.type = NoticeEvent.NoticeType.GROUP_POKE
}) this.time = time
this.groupPoke = GroupPokeNotice.newBuilder().apply {
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 return true
} }
@ -264,20 +257,20 @@ internal object GlobalEventTransmitter: QQInterfaces() {
groupCode: Long, groupCode: Long,
operator: Long, operator: Long,
operatorUid: String, operatorUid: String,
type: GroupMemberIncreasedType type: GroupMemberIncreasedNotice.GroupMemberIncreasedType
): Boolean { ): Boolean {
pushNotice(noticeEvent { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_MEMBER_INCREASE this.type = NoticeEvent.NoticeType.GROUP_MEMBER_INCREASE
this.time = time.toInt() this.time = time
this.groupMemberIncrease = groupMemberIncreasedNotice { this.groupMemberIncrease = GroupMemberIncreasedNotice.newBuilder().apply {
this.groupId = groupCode this.groupId = groupCode
this.operatorUid = operatorUid this.operatorUid = operatorUid
this.operatorUin = operator this.operatorUin = operator
this.targetUid = targetUid this.targetUid = targetUid
this.targetUin = target this.targetUin = target
this.type = type this.type = type
} }.build()
}) }.build())
return true return true
} }
@ -288,20 +281,20 @@ internal object GlobalEventTransmitter: QQInterfaces() {
groupCode: Long, groupCode: Long,
operator: Long, operator: Long,
operatorUid: String, operatorUid: String,
type: GroupMemberDecreasedType type: GroupMemberDecreasedNotice.GroupMemberDecreasedType
): Boolean { ): Boolean {
pushNotice(noticeEvent { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_MEMBER_INCREASE this.type = NoticeEvent.NoticeType.GROUP_MEMBER_DECREASE
this.time = time.toInt() this.time = time
this.groupMemberDecrease = groupMemberDecreasedNotice { this.groupMemberDecrease = GroupMemberDecreasedNotice.newBuilder().apply {
this.groupId = groupCode this.groupId = groupCode
this.operatorUid = operatorUid this.operatorUid = operatorUid
this.operatorUin = operator this.operatorUin = operator
this.targetUid = targetUid this.targetUid = targetUid
this.targetUin = target this.targetUin = target
this.type = type this.type = type
} }.build()
}) }.build())
return true return true
} }
@ -312,34 +305,36 @@ internal object GlobalEventTransmitter: QQInterfaces() {
groupCode: Long, groupCode: Long,
setAdmin: Boolean setAdmin: Boolean
): Boolean { ): Boolean {
pushNotice(noticeEvent { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_ADMIN_CHANGED this.type = NoticeEvent.NoticeType.GROUP_ADMIN_CHANGED
this.time = msgTime.toInt() this.time = msgTime
this.groupAdminChanged = groupAdminChangedNotice { this.groupAdminChanged = GroupAdminChangedNotice.newBuilder().apply {
this.groupId = groupCode this.groupId = groupCode
this.targetUid = targetUid this.targetUid = targetUid
this.targetUin = target this.targetUin = target
this.isAdmin = setAdmin this.isAdmin = setAdmin
} }.build()
}) }.build())
return true return true
} }
suspend fun transGroupWholeBan( suspend fun transGroupWholeBan(
msgTime: Long, msgTime: Long,
operator: Long,
groupCode: Long, groupCode: Long,
operatorUid: String,
operator: Long,
isOpen: Boolean isOpen: Boolean
): Boolean { ): Boolean {
pushNotice(noticeEvent { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_WHOLE_BAN this.type = NoticeEvent.NoticeType.GROUP_WHOLE_BAN
this.time = msgTime.toInt() this.time = msgTime
this.groupWholeBan = groupWholeBanNotice { this.groupWholeBan = GroupWholeBanNotice.newBuilder().apply {
this.groupId = groupCode this.groupId = groupCode
this.isWholeBan = isOpen this.isBan = isOpen
this.operator = operator this.operatorUid = operatorUid
} this.operatorUin = operator
}) }.build()
}.build())
return true return true
} }
@ -352,20 +347,20 @@ internal object GlobalEventTransmitter: QQInterfaces() {
groupCode: Long, groupCode: Long,
duration: Int duration: Int
): Boolean { ): Boolean {
pushNotice(noticeEvent { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_MEMBER_BANNED this.type = NoticeEvent.NoticeType.GROUP_MEMBER_BAN
this.time = msgTime.toInt() this.time = msgTime
this.groupMemberBanned = groupMemberBannedNotice { this.groupMemberBan = GroupMemberBanNotice.newBuilder().apply {
this.groupId = groupCode this.groupId = groupCode
this.operatorUid = operatorUid this.operatorUid = operatorUid
this.operatorUin = operator this.operatorUin = operator
this.targetUid = targetUid this.targetUid = targetUid
this.targetUin = target this.targetUin = target
this.duration = duration this.duration = duration
this.type = if (duration > 0) GroupMemberBanType.BAN this.type = if (duration > 0) GroupMemberBanNotice.GroupMemberBanType.BAN
else GroupMemberBanType.LIFT_BAN else GroupMemberBanNotice.GroupMemberBanType.LIFT_BAN
} }.build()
}) }.build())
return true return true
} }
@ -379,48 +374,56 @@ internal object GlobalEventTransmitter: QQInterfaces() {
msgId: Long, msgId: Long,
tipText: String tipText: String
): Boolean { ): Boolean {
pushNotice(noticeEvent { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_RECALL this.type = NoticeEvent.NoticeType.GROUP_RECALL
this.time = time.toInt() this.time = time
this.groupRecall = groupRecallNotice { this.groupRecall = GroupRecallNotice.newBuilder().apply {
this.groupId = groupCode this.groupId = groupCode
this.operatorUid = operatorUid this.operatorUid = operatorUid
this.operatorUin = operator this.operatorUin = operator
this.targetUid = targetUid this.targetUid = targetUid
this.targetUin = target this.targetUin = target
this.messageId = msgId this.messageId = msgId.toString()
this.tipText = tipText this.tipText = tipText
} }.build()
}) }.build())
return true return true
} }
suspend fun transCardChange( suspend fun transCardChange(
time: Long, time: Long,
targetId: Long, targetId: Long,
oldCard: String,
newCard: String, newCard: String,
groupId: Long groupId: Long
): Boolean { ): Boolean {
pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeEvent.NoticeType.GROUP_CARD_CHANGED
this.time = time
this.groupCardChanged = GroupCardChangedNotice.newBuilder().apply {
this.groupId = groupId
this.targetUin = targetId
this.newCard = newCard
}.build()
}.build())
return true return true
} }
suspend fun transTitleChange( suspend fun transTitleChange(
time: Long, time: Long,
targetId: Long, targetUin: Long,
title: String, title: String,
groupId: Long groupId: Long
): Boolean { ): Boolean {
pushNotice(noticeEvent { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_MEMBER_UNIQUE_TITLE_CHANGED this.type = NoticeEvent.NoticeType.GROUP_MEMBER_UNIQUE_TITLE_CHANGED
this.time = time.toInt() this.time = time
this.groupMemberUniqueTitleChanged = groupUniqueTitleChangedNotice { this.groupMemberUniqueTitleChanged = GroupUniqueTitleChangedNotice.newBuilder().apply {
this.groupId = groupId this.groupId = groupId
this.target = targetId this.targetUid = ContactHelper.getUidByUinAsync(targetUin)
this.targetUin = targetUin
this.title = title this.title = title
} }.build()
}) }.build())
return true return true
} }
@ -432,17 +435,19 @@ internal object GlobalEventTransmitter: QQInterfaces() {
groupId: Long, groupId: Long,
subType: UInt subType: UInt
): Boolean { ): Boolean {
pushNotice(noticeEvent { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.GROUP_ESSENCE_CHANGED this.type = NoticeEvent.NoticeType.GROUP_ESSENCE_CHANGED
this.time = time.toInt() this.time = time
this.groupEssenceChanged = essenceMessageNotice { this.groupEssenceChanged = GroupEssenceMessageNotice.newBuilder().apply {
this.groupId = groupId this.groupId = groupId
this.messageId = msgId this.messageId = msgId.toString()
this.sender = senderUin this.targetUid = ContactHelper.getUidByUinAsync(targetUin)
this.operator = operatorUin this.targetUin = senderUin
this.subType = subType.toInt() this.operatorUid = ContactHelper.getUidByUinAsync(operatorUin)
} this.operatorUin = operatorUin
}) this.isSet = subType.toInt() == 1
}.build()
}.build())
return true return true
} }
} }
@ -451,31 +456,37 @@ internal object GlobalEventTransmitter: QQInterfaces() {
* 私聊通知 通知器 * 私聊通知 通知器
*/ */
object PrivateNoticeTransmitter { object PrivateNoticeTransmitter {
suspend fun transPrivatePoke(msgTime: Long, operator: Long, target: Long, action: String?, suffix: String?, actionImg: String?): Boolean { suspend fun transPrivatePoke(
pushNotice(noticeEvent { msgTime: Long,
this.type = NoticeType.FRIEND_POKE operator: Long,
this.time = msgTime.toInt() action: String?,
this.friendPoke = friendPokeNotice { suffix: String?,
actionImg: String?
): Boolean {
pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeEvent.NoticeType.PRIVATE_POKE
this.time = msgTime
this.privatePoke = PrivatePokeNotice.newBuilder().apply {
this.action = action ?: "" this.action = action ?: ""
this.target = target this.operatorUid = ContactHelper.getUidByUinAsync(operator)
this.operator = operator this.operatorUin = operator
this.suffix = suffix ?: "" this.suffix = suffix ?: ""
this.actionImage = actionImg ?: "" this.actionImage = actionImg ?: ""
} }.build()
}) }.build())
return true return true
} }
suspend fun transPrivateRecall(time: Long, operator: Long, msgId: Long, tipText: String): Boolean { suspend fun transPrivateRecall(time: Long, operator: Long, msgId: Long, tipText: String): Boolean {
pushNotice(noticeEvent { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeType.FRIEND_RECALL this.type = NoticeEvent.NoticeType.PRIVATE_RECALL
this.time = time.toInt() this.time = time
this.friendRecall = friendRecallNotice { this.privateRecall = PrivateRecallNotice.newBuilder().apply {
this.operator = operator this.operatorUin = operator
this.messageId = msgId this.messageId = msgId.toString()
this.tipText = tipText this.tipText = tipText
} }.build()
}) }.build())
return true return true
} }
@ -485,46 +496,65 @@ internal object GlobalEventTransmitter: QQInterfaces() {
* 请求 通知器 * 请求 通知器
*/ */
object RequestTransmitter { object RequestTransmitter {
suspend fun transFriendApp(time: Long, operator: Long, tipText: String, flag: String): Boolean { suspend fun transFriendApp(time: Long, applierUid: String, operator: Long, tipText: String, flag: String): Boolean {
pushRequest(requestsEvent { pushRequest(RequestEvent.newBuilder().apply {
this.type = RequestType.FRIEND_APPLY this.type = RequestEvent.RequestType.FRIEND_APPLY
this.time = time.toInt() this.time = time
this.friendApply = friendApplyRequest { this.requestId = flag
this.friendApply = FriendApplyRequest.newBuilder().apply {
this.applierUid = applierUid
this.applierUin = operator this.applierUin = operator
this.message = tipText this.message = tipText
this.flag = flag }.build()
} }.build())
})
return true return true
} }
suspend fun transGroupApply( suspend fun transGroupApply(
time: Long, time: Long,
applier: Long, applierUin: Long,
applierUid: String, applierUid: String,
reason: String, reason: String,
groupCode: Long, groupCode: Long,
flag: String, flag: String
type: GroupApplyType
): Boolean { ): Boolean {
pushRequest(requestsEvent { pushRequest(RequestEvent.newBuilder().apply {
this.type = RequestType.GROUP_APPLY this.type = RequestEvent.RequestType.GROUP_APPLY
this.time = time.toInt() this.time = time
this.groupApply = groupApplyRequest { this.requestId = flag
this.groupApply = GroupApplyRequest.newBuilder().apply {
this.applierUid = applierUid this.applierUid = applierUid
this.applierUin = applier this.applierUin = applierUin
this.groupId = groupCode this.groupId = groupCode
this.reason = reason this.reason = reason
this.flag = flag }.build()
this.type = type }.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
this.requestId = flag
this.invitedGroup = InvitedJoinGroupRequest.newBuilder().apply {
this.inviterUid = inviterUid
this.inviterUin = inviterUin
this.groupId = groupCode
}.build()
}.build())
return true return true
} }
} }
suspend inline fun onMessageEvent(collector: FlowCollector<Pair<MsgRecord, MessageEvent>>) { suspend inline fun onMessageEvent(collector: FlowCollector<Pair<MsgRecord, PushMessageBody>>) {
messageEventFlow.collect { MessageEventFlow.collect {
GlobalScope.launch { GlobalScope.launch {
collector.emit(it) collector.emit(it)
} }
@ -539,7 +569,7 @@ internal object GlobalEventTransmitter: QQInterfaces() {
} }
} }
suspend inline fun onRequestEvent(collector: FlowCollector<RequestsEvent>) { suspend inline fun onRequestEvent(collector: FlowCollector<RequestEvent>) {
requestEventFlow.collect { requestEventFlow.collect {
GlobalScope.launch { GlobalScope.launch {
collector.emit(it) collector.emit(it)

View File

@ -0,0 +1,33 @@
package moe.fuqiuluo.shamrock.tools
import com.tencent.qphone.base.remote.FromServiceMsg
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.utils.DeflateTools
import moe.fuqiuluo.symbols.decodeProtobuf
import protobuf.oidb.TrpcOidb
import tencent.im.oidb.oidb_sso
fun FromServiceMsg.decodeToOidb(): oidb_sso.OIDBSSOPkg {
return kotlin.runCatching {
oidb_sso.OIDBSSOPkg().mergeFrom(wupBuffer.slice(4).let {
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
})
}.getOrElse {
oidb_sso.OIDBSSOPkg().mergeFrom(wupBuffer.let {
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
})
}
}
fun FromServiceMsg.decodeToTrpcOidb(): TrpcOidb {
return kotlin.runCatching {
wupBuffer.slice(4).let {
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
}.decodeProtobuf<TrpcOidb>()
}.getOrElse {
wupBuffer.let {
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
}.decodeProtobuf<TrpcOidb>()
}
}

View File

@ -6,17 +6,21 @@ import android.content.Context
import android.content.Context.BATTERY_SERVICE import android.content.Context.BATTERY_SERVICE
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.os.BatteryManager import android.os.BatteryManager
import android.os.Build import android.os.Build
import android.os.Process import android.os.Process
import android.provider.Settings import android.provider.Settings
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import moe.fuqiuluo.shamrock.tools.ShamrockVersion
import mqq.app.MobileQQ import mqq.app.MobileQQ
import kotlin.random.Random import kotlin.random.Random
internal object PlatformUtils { internal object PlatformUtils {
const val QQ_9_0_8_VER = 5540 const val QQ_9_0_8_VER = 5540
const val QQ_9_0_65_VER = 6566
fun getQUA(): String { fun getQUA(): String {
return "V1_AND_SQ_${getQQVersion(MobileQQ.getContext())}_${getQQVersionCode()}_YYB_D" return "V1_AND_SQ_${getQQVersion(MobileQQ.getContext())}_${getQQVersionCode()}_YYB_D"
@ -69,6 +73,15 @@ internal object PlatformUtils {
return MobileQQ.getMobileQQ().qqProcessName == "com.tencent.tim" return MobileQQ.getMobileQQ().qqProcessName == "com.tencent.tim"
} }
fun isApkInDebug(context: Context): Boolean {
try {
val info = context.applicationInfo
return (info.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
} catch (e: Exception) {
return false
}
}
fun killProcess(context: Context, processName: String) { fun killProcess(context: Context, processName: String) {
for (processInfo in (context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).runningAppProcesses) { for (processInfo in (context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).runningAppProcesses) {
if (processInfo.processName == processName) { if (processInfo.processName == processName) {

View File

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

View File

@ -6,8 +6,11 @@ import android.content.Context
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kritor.client.KritorClient
import kritor.server.KritorServer import kritor.server.KritorServer
import moe.fuqiuluo.shamrock.config.ActiveRPC import moe.fuqiuluo.shamrock.config.ActiveRPC
import moe.fuqiuluo.shamrock.config.PassiveRPC
import moe.fuqiuluo.shamrock.config.RPCAddress
import moe.fuqiuluo.shamrock.config.RPCPort import moe.fuqiuluo.shamrock.config.RPCPort
import moe.fuqiuluo.shamrock.config.ShamrockConfig import moe.fuqiuluo.shamrock.config.ShamrockConfig
import moe.fuqiuluo.shamrock.config.get import moe.fuqiuluo.shamrock.config.get
@ -17,6 +20,7 @@ import moe.fuqiuluo.symbols.Process
import moe.fuqiuluo.symbols.XposedHook import moe.fuqiuluo.symbols.XposedHook
private lateinit var server: KritorServer private lateinit var server: KritorServer
private lateinit var client: KritorClient
@XposedHook(Process.MAIN, priority = 10) @XposedHook(Process.MAIN, priority = 10)
internal class InitRemoteService : IAction { internal class InitRemoteService : IAction {
@ -32,6 +36,21 @@ internal class InitRemoteService : IAction {
LogCenter.log("ActiveRPC is disabled, KritorServer will not be started.") LogCenter.log("ActiveRPC is disabled, KritorServer will not be started.")
} }
if (PassiveRPC.get()) {
if (!::client.isInitialized) {
val hostAndPort = RPCAddress.get().split(":").let {
it.first() to it.last().toInt()
}
LogCenter.log("Connect RPC to ${hostAndPort.first}:${hostAndPort.second}")
client = KritorClient(hostAndPort.first, hostAndPort.second)
client.start()
client.listen()
}
} else {
LogCenter.log("PassiveRPC is disabled, KritorServer will not be started.")
}
}.onFailure { }.onFailure {
LogCenter.log("Start RPC failed: ${it.message}", Level.ERROR) LogCenter.log("Start RPC failed: ${it.message}", Level.ERROR)

View File

@ -9,11 +9,13 @@ import qq.service.QQInterfaces
object SwitchStatus: IInteract, QQInterfaces() { object SwitchStatus: IInteract, QQInterfaces() {
override fun invoke(intent: Intent) { override fun invoke(intent: Intent) {
if (app.isLogin) {
AppTalker.talk("switch_status") { AppTalker.talk("switch_status") {
put("account", app.currentAccountUin) 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("voice", NativeLoader.isVoiceLoaded)
put("core_version", ShamrockVersion) put("core_version", ShamrockVersion)
} }
} }
}
} }

View File

@ -83,6 +83,17 @@ abstract class QQInterfaces {
app.sendToService(to) app.sendToService(to)
} }
fun sendBuffer(
cmd: String,
isProto: Boolean,
data: ByteArray,
) {
val toServiceMsg = createToServiceMsg(cmd)
toServiceMsg.putWupBuffer(data)
toServiceMsg.addAttribute("req_pb_protocol_flag", isProto)
sendToServiceMsg(toServiceMsg)
}
@DelicateCoroutinesApi @DelicateCoroutinesApi
suspend fun sendBufferAW( suspend fun sendBufferAW(
cmd: String, cmd: String,

View File

@ -5,7 +5,7 @@ import androidx.exifinterface.media.ExifInterface
import com.tencent.mobileqq.qroute.QRoute import com.tencent.mobileqq.qroute.QRoute
import com.tencent.qqnt.aio.adapter.api.IAIOPttApi import com.tencent.qqnt.aio.adapter.api.IAIOPttApi
import com.tencent.qqnt.kernel.nativeinterface.CommonFileInfo import com.tencent.qqnt.kernel.nativeinterface.CommonFileInfo
import com.tencent.qqnt.kernel.nativeinterface.Contact import com.tencent.qqnt.kernelpublic.nativeinterface.Contact
import com.tencent.qqnt.kernel.nativeinterface.FileTransNotifyInfo import com.tencent.qqnt.kernel.nativeinterface.FileTransNotifyInfo
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MsgElement import com.tencent.qqnt.kernel.nativeinterface.MsgElement
@ -20,9 +20,12 @@ import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.shamrock.config.ResourceGroup import moe.fuqiuluo.shamrock.config.ResourceGroup
import moe.fuqiuluo.shamrock.config.ShamrockConfig import moe.fuqiuluo.shamrock.config.ShamrockConfig
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.tools.decodeToTrpcOidb
import moe.fuqiuluo.shamrock.tools.hex2ByteArray import moe.fuqiuluo.shamrock.tools.hex2ByteArray
import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
import moe.fuqiuluo.shamrock.tools.slice import moe.fuqiuluo.shamrock.tools.slice
import moe.fuqiuluo.shamrock.tools.toHexString
import moe.fuqiuluo.shamrock.utils.AudioUtils import moe.fuqiuluo.shamrock.utils.AudioUtils
import moe.fuqiuluo.shamrock.utils.FileUtils import moe.fuqiuluo.shamrock.utils.FileUtils
import moe.fuqiuluo.shamrock.utils.MediaType import moe.fuqiuluo.shamrock.utils.MediaType
@ -34,6 +37,8 @@ import protobuf.oidb.cmd0x11c5.CodecConfigReq
import protobuf.oidb.cmd0x11c5.CommonHead import protobuf.oidb.cmd0x11c5.CommonHead
import protobuf.oidb.cmd0x11c5.DownloadExt import protobuf.oidb.cmd0x11c5.DownloadExt
import protobuf.oidb.cmd0x11c5.DownloadReq import protobuf.oidb.cmd0x11c5.DownloadReq
import protobuf.oidb.cmd0x11c5.DownloadRkeyReq
import protobuf.oidb.cmd0x11c5.DownloadRkeyRsp
import protobuf.oidb.cmd0x11c5.FileInfo import protobuf.oidb.cmd0x11c5.FileInfo
import protobuf.oidb.cmd0x11c5.FileType import protobuf.oidb.cmd0x11c5.FileType
import protobuf.oidb.cmd0x11c5.IndexNode import protobuf.oidb.cmd0x11c5.IndexNode
@ -65,7 +70,7 @@ import kotlin.time.Duration.Companion.seconds
internal object NtV2RichMediaSvc: QQInterfaces() { internal object NtV2RichMediaSvc: QQInterfaces() {
private val requestIdSeq = atomic(1L) private val requestIdSeq = atomic(1L)
private fun fetchGroupResUploadTo(): String { fun fetchGroupResUploadTo(): String {
return ShamrockConfig[ResourceGroup].ifNullOrEmpty { "100000000" }!! return ShamrockConfig[ResourceGroup].ifNullOrEmpty { "100000000" }!!
} }
@ -283,6 +288,44 @@ internal object NtV2RichMediaSvc: QQInterfaces() {
return Result.success(result) return Result.success(result)
} }
suspend fun getTempNtRKey(): Result<DownloadRkeyRsp> {
runCatching {
val req = NtV2RichMediaReq(
head = MultiMediaReqHead(
commonHead = CommonHead(
requestId = requestIdSeq.incrementAndGet().toULong(),
cmd = 202u
),
sceneInfo = SceneInfo(
requestType = 2u,
businessType = 1u,
sceneType = 0u,
),
clientMeta = ClientMeta(2u)
),
downloadRkey = DownloadRkeyReq(
types = listOf(10, 20),
downloadType = 2
)
).toByteArray()
val fromServiceMsg = sendOidbAW("OidbSvcTrpcTcp.0x9067_202", 0x9067, 202, req, true)
if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) {
return Result.failure(Exception("failed to fetch NtTempRKey: ${fromServiceMsg?.wupBuffer?.toHexString()}"))
}
val trpc = fromServiceMsg.decodeToTrpcOidb()
if (trpc.buffer == null) {
return Result.failure(Exception("failed to fetch NtTempRKey: ${trpc.msg}"))
}
trpc.buffer?.decodeProtobuf<NtV2RichMediaRsp>()?.downloadRkeyRsp?.let {
return Result.success(it)
}
}.onFailure {
return Result.failure(it)
}
return Result.failure(Exception("failed to fetch NtTempRKey"))
}
/** /**
* 获取NT图片的RKEY * 获取NT图片的RKEY
*/ */
@ -351,9 +394,14 @@ internal object NtV2RichMediaSvc: QQInterfaces() {
).toByteArray() ).toByteArray()
val fromServiceMsg = sendOidbAW("OidbSvcTrpcTcp.0x11c5_200", 4549, 200, req, true) val fromServiceMsg = sendOidbAW("OidbSvcTrpcTcp.0x11c5_200", 4549, 200, req, true)
if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) { if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) {
return Result.failure(Exception("unable to get multimedia pic info: ${fromServiceMsg?.wupBuffer}")) return Result.failure(Exception("unable to get multimedia pic info: ${fromServiceMsg?.wupBuffer?.toHexString()}"))
} }
fromServiceMsg.wupBuffer.slice(4).decodeProtobuf<TrpcOidb>().buffer.decodeProtobuf<NtV2RichMediaRsp>().download?.rkeyParam?.let { val trpc = fromServiceMsg.decodeToTrpcOidb()
if (trpc.buffer == null) {
return Result.failure(Exception("unable to get multimedia pic info: ${trpc.msg}"))
}
trpc.buffer?.decodeProtobuf<NtV2RichMediaRsp>()?.download?.rkeyParam?.let {
return Result.success(it) return Result.success(it)
} }
}.onFailure { }.onFailure {
@ -370,13 +418,14 @@ internal object NtV2RichMediaSvc: QQInterfaces() {
width: UInt, width: UInt,
height: UInt, height: UInt,
retryCnt: Int, retryCnt: Int,
chatType: Int,
sceneBuilder: suspend SceneInfo.() -> Unit sceneBuilder: suspend SceneInfo.() -> Unit
): Result<UploadRsp> { ): Result<UploadRsp> {
return runCatching { return runCatching {
requestUploadNtPic(file, md5, sha, name, width, height, sceneBuilder).getOrThrow() requestUploadNtPic(file, md5, sha, name, width, height, chatType, sceneBuilder).getOrThrow()
}.onFailure { }.onFailure {
if (retryCnt > 0) { if (retryCnt > 0) {
return requestUploadNtPic(file, md5, sha, name, width, height, retryCnt - 1, sceneBuilder) return requestUploadNtPic(file, md5, sha, name, width, height, retryCnt - 1, chatType, sceneBuilder)
} }
} }
} }
@ -388,6 +437,7 @@ internal object NtV2RichMediaSvc: QQInterfaces() {
name: String, name: String,
width: UInt, width: UInt,
height: UInt, height: UInt,
chatType: Int,
sceneBuilder: suspend SceneInfo.() -> Unit sceneBuilder: suspend SceneInfo.() -> Unit
): Result<UploadRsp> { ): Result<UploadRsp> {
val req = NtV2RichMediaReq( val req = NtV2RichMediaReq(
@ -427,17 +477,28 @@ internal object NtV2RichMediaSvc: QQInterfaces() {
tryFastUploadCompleted = true, tryFastUploadCompleted = true,
srvSendMsg = false, srvSendMsg = false,
clientRandomId = Random.nextULong(), clientRandomId = Random.nextULong(),
compatQMsgSceneType = 1u, compatQMsgSceneType = 2u,
clientSeq = Random.nextUInt(), clientSeq = Random.nextUInt(),
noNeedCompatMsg = false noNeedCompatMsg = true
) )
).toByteArray() ).toByteArray()
val fromServiceMsg = sendOidbAW("OidbSvcTrpcTcp.0x11c5_100", 4549, 100, req, true, timeout = 3.seconds) val fromServiceMsg = when (chatType) {
MsgConstant.KCHATTYPEGROUP -> {
sendOidbAW("OidbSvcTrpcTcp.0x11c4_100", 4548, 100, req, true, timeout = 3.seconds)
}
MsgConstant.KCHATTYPEC2C -> {
sendOidbAW("OidbSvcTrpcTcp.0x11c5_100", 4549, 100, req, true, timeout = 3.seconds)
}
else -> return Result.failure(Exception("unknown chat type: $chatType"))
}
if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) { if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) {
return Result.failure(Exception("unable to request upload nt pic")) return Result.failure(Exception("unable to request upload nt pic"))
} }
val rspBuffer = fromServiceMsg.wupBuffer.slice(4).decodeProtobuf<TrpcOidb>().buffer val trpc = fromServiceMsg.decodeToTrpcOidb()
val rsp = rspBuffer.decodeProtobuf<NtV2RichMediaRsp>() if (trpc.buffer == null) {
return Result.failure(Exception("unable to request upload nt pic: ${trpc.msg}"))
}
val rsp = trpc.buffer!!.decodeProtobuf<NtV2RichMediaRsp>()
if (rsp.upload == null) { if (rsp.upload == null) {
return Result.failure(Exception("unable to request upload nt pic: ${rsp.head}")) return Result.failure(Exception("unable to request upload nt pic: ${rsp.head}"))
} }

View File

@ -0,0 +1,58 @@
package qq.service.bdh
import com.tencent.mobileqq.data.MessageRecord
import java.io.File
internal enum class ContactType {
TROOP,
PRIVATE,
}
internal interface TransTarget {
val id: String
val type: ContactType
val mRec: MessageRecord?
}
internal class Troop(
override val id: String,
override val mRec: MessageRecord? = null
): TransTarget {
override val type: ContactType = ContactType.TROOP
}
internal class Private(
override val id: String,
override val mRec: MessageRecord? = null
): TransTarget {
override val type: ContactType = ContactType.PRIVATE
}
internal enum class ResourceType {
Picture,
Video,
Voice
}
internal interface Resource {
val type: ResourceType
}
internal data class PictureResource(
val src: File
): Resource {
override val type = ResourceType.Picture
}
internal data class VideoResource(
val src: File, val thumb: File
): Resource {
override val type = ResourceType.Video
}
internal data class VoiceResource(
val src: File
): Resource {
override val type = ResourceType.Voice
}

View File

@ -6,11 +6,16 @@ import com.tencent.mobileqq.transfile.FileMsg
import com.tencent.mobileqq.transfile.api.IProtoReqManager import com.tencent.mobileqq.transfile.api.IProtoReqManager
import com.tencent.mobileqq.transfile.protohandler.RichProto import com.tencent.mobileqq.transfile.protohandler.RichProto
import com.tencent.mobileqq.transfile.protohandler.RichProtoProc import com.tencent.mobileqq.transfile.protohandler.RichProtoProc
import com.tencent.qqnt.kernel.nativeinterface.Image
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
import com.tencent.qqnt.kernel.nativeinterface.PicElement
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.tools.slice import moe.fuqiuluo.shamrock.tools.decodeToOidb
import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
import moe.fuqiuluo.shamrock.tools.toHexString import moe.fuqiuluo.shamrock.tools.toHexString
import moe.fuqiuluo.shamrock.utils.PlatformUtils import moe.fuqiuluo.shamrock.utils.PlatformUtils
import moe.fuqiuluo.symbols.decodeProtobuf import moe.fuqiuluo.symbols.decodeProtobuf
@ -28,7 +33,6 @@ import qq.service.contact.ContactHelper
import tencent.im.cs.cmd0x346.cmd0x346 import tencent.im.cs.cmd0x346.cmd0x346
import tencent.im.oidb.cmd0x6d6.oidb_0x6d6 import tencent.im.oidb.cmd0x6d6.oidb_0x6d6
import tencent.im.oidb.cmd0xe37.cmd0xe37 import tencent.im.oidb.cmd0xe37.cmd0xe37
import tencent.im.oidb.oidb_sso
import kotlin.coroutines.resume import kotlin.coroutines.resume
private const val GPRO_PIC = "gchat.qpic.cn" private const val GPRO_PIC = "gchat.qpic.cn"
@ -53,8 +57,7 @@ internal object RichProtoSvc: QQInterfaces() {
if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) { if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) {
return "" return ""
} }
val body = oidb_sso.OIDBSSOPkg() val body = fromServiceMsg.decodeToOidb()
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
body.bytes_bodybuffer body.bytes_bodybuffer
.get().toByteArray() .get().toByteArray()
.decodeProtobuf<Oidb0xfc2RspBody>() .decodeProtobuf<Oidb0xfc2RspBody>()
@ -82,8 +85,7 @@ internal object RichProtoSvc: QQInterfaces() {
if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) { if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) {
return "" return ""
} }
val body = oidb_sso.OIDBSSOPkg() val body = fromServiceMsg.decodeToOidb()
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
val result = oidb_0x6d6.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray()) val result = oidb_0x6d6.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
if (body.uint32_result.get() != 0 if (body.uint32_result.get() != 0
|| result.download_file_rsp.int32_ret_code.get() != 0) { || result.download_file_rsp.int32_ret_code.get() != 0) {
@ -130,8 +132,7 @@ internal object RichProtoSvc: QQInterfaces() {
} }
return "" return ""
} else { } else {
val body = oidb_sso.OIDBSSOPkg() val body = fromServiceMsg.decodeToOidb()
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
val result = cmd0x346.RspBody().mergeFrom(cmd0xe37.Resp0xe37().mergeFrom( val result = cmd0x346.RspBody().mergeFrom(cmd0xe37.Resp0xe37().mergeFrom(
body.bytes_bodybuffer.get().toByteArray() body.bytes_bodybuffer.get().toByteArray()
).bytes_cmd_0x346_rsp_body.get().toByteArray()) ).bytes_cmd_0x346_rsp_body.get().toByteArray())
@ -152,6 +153,75 @@ internal object RichProtoSvc: QQInterfaces() {
} }
} }
suspend fun getTempPicDownloadUrl(
chatType: Int,
originalUrl: String,
md5: String,
image: PicElement,
storeId: Int = 0,
peer: String? = null,
subPeer: String? = null,
): String {
val isNtServer = originalUrl.startsWith("/download")
if (isNtServer) {
val tmpRKey = NtV2RichMediaSvc.getTempNtRKey()
if (tmpRKey.isSuccess) {
val tmpRKeyRsp = tmpRKey.getOrThrow()
val tmpRKeyMap = hashMapOf<UInt, String>()
tmpRKeyRsp.rkeys?.forEach { rKeyInfo ->
tmpRKeyMap[rKeyInfo.type] = rKeyInfo.rkey
}
val rkey = tmpRKeyMap[when(chatType) {
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> 10u
MsgConstant.KCHATTYPEC2C -> 20u
MsgConstant.KCHATTYPEGUILD -> 10u
else -> 0u
}]
if (rkey != null) {
return "https://$MULTIMEDIA_DOMAIN$originalUrl$rkey"
}
}
}
return when (chatType) {
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> getGroupPicDownUrl(
originalUrl = originalUrl,
md5 = md5,
fileId = image.fileUuid,
width = image.picWidth.toUInt(),
height = image.picHeight.toUInt(),
sha = "",
fileSize = image.fileSize.toULong(),
peer = peer ?: "0"
)
MsgConstant.KCHATTYPEC2C -> getC2CPicDownUrl(
originalUrl = originalUrl,
md5 = md5,
fileId = image.fileUuid,
width = image.picWidth.toUInt(),
height = image.picHeight.toUInt(),
sha = "",
fileSize = image.fileSize.toULong(),
peer = peer ?: "0",
storeId = storeId
)
MsgConstant.KCHATTYPEGUILD -> getGuildPicDownUrl(
originalUrl = originalUrl,
md5 = md5,
fileId = image.fileUuid,
width = image.picWidth.toUInt(),
height = image.picHeight.toUInt(),
sha = "",
fileSize = image.fileSize.toULong(),
peer = peer ?: "0",
subPeer = subPeer ?: "0"
)
else -> throw UnsupportedOperationException("Not supported chat type: $chatType")
}
}
suspend fun getGroupPicDownUrl( suspend fun getGroupPicDownUrl(
originalUrl: String, originalUrl: String,
md5: String, md5: String,

View File

@ -0,0 +1,135 @@
package qq.service.bdh
import com.tencent.mobileqq.data.MessageForShortVideo
import com.tencent.mobileqq.data.MessageRecord
import com.tencent.mobileqq.transfile.FileMsg
import com.tencent.mobileqq.transfile.TransferRequest
import moe.fuqiuluo.shamrock.utils.MD5
import qq.service.bdh.ResourceType.*
import java.io.File
internal object Transfer: FileTransfer() {
private val ROUTE = mapOf<ContactType, Map<ResourceType, suspend TransTarget.(Resource) -> Boolean>>(
ContactType.TROOP to mapOf(
Picture to { uploadGroupPic(id, (it as PictureResource).src, mRec) },
Voice to { uploadGroupVoice(id, (it as VoiceResource).src) },
Video to { uploadGroupVideo(id, (it as VideoResource).src, it.thumb) },
),
ContactType.PRIVATE to mapOf(
Picture to { uploadC2CPic(id, (it as PictureResource).src, mRec) },
Voice to { uploadC2CVoice(id, (it as VoiceResource).src) },
Video to { uploadC2CVideo(id, (it as VideoResource).src, it.thumb) },
)
)
suspend fun uploadC2CVideo(
userId: String,
file: File,
thumb: File,
wait: Boolean = true
): Boolean {
return transC2CResource(userId, file, FileMsg.TRANSFILE_TYPE_SHORT_VIDEO_C2C, BUSI_TYPE_SHORT_VIDEO, wait) {
it.mSourceVideoCodecFormat = VIDEO_FORMAT_MP4
it.mRec = MessageForShortVideo().also {
it.busiType = BUSI_TYPE_SHORT_VIDEO
}
it.mThumbPath = thumb.absolutePath
it.mThumbMd5 = MD5.genFileMd5Hex(thumb.absolutePath)
}
}
suspend fun uploadGroupVideo(
groupId: String,
file: File,
thumb: File,
wait: Boolean = true
): Boolean {
return transTroopResource(groupId, file, FileMsg.TRANSFILE_TYPE_SHORT_VIDEO_TROOP, BUSI_TYPE_SHORT_VIDEO, wait) {
it.mSourceVideoCodecFormat = VIDEO_FORMAT_MP4
it.mRec = MessageForShortVideo().also {
it.busiType = BUSI_TYPE_SHORT_VIDEO
}
it.mThumbPath = thumb.absolutePath
it.mThumbMd5 = MD5.genFileMd5Hex(thumb.absolutePath)
}
}
suspend fun uploadC2CVoice(
userId: String,
file: File,
wait: Boolean = true
): Boolean {
return transC2CResource(userId, file, FileMsg.TRANSFILE_TYPE_PTT, 1002, wait) {
it.mPttUploadPanel = 3
it.mPttCompressFinish = true
it.mIsPttPreSend = true
}
}
suspend fun uploadGroupVoice(
groupId: String,
file: File,
wait: Boolean = true
): Boolean {
return transTroopResource(groupId, file, FileMsg.TRANSFILE_TYPE_PTT, 1002, wait) {
it.mPttUploadPanel = 3
it.mPttCompressFinish = true
it.mIsPttPreSend = true
}
}
suspend fun uploadC2CPic(
peerId: String,
file: File,
record: MessageRecord? = null,
wait: Boolean = true
): Boolean {
return transC2CResource(peerId, file, FileMsg.TRANSFILE_TYPE_PIC, SEND_MSG_BUSINESS_TYPE_PIC_CAMERA, wait) {
val picUpExtraInfo = TransferRequest.PicUpExtraInfo()
picUpExtraInfo.mIsRaw = false
picUpExtraInfo.mUinType = FileMsg.UIN_BUDDY
it.mPicSendSource = 8
it.mExtraObj = picUpExtraInfo
it.mIsPresend = true
it.delayShowProgressTimeInMs = 2000
it.mRec = record
}
}
suspend fun uploadGroupPic(
groupId: String,
file: File,
record: MessageRecord? = null,
wait: Boolean = true
): Boolean {
return transTroopResource(groupId, file, FileMsg.TRANSFILE_TYPE_PIC, SEND_MSG_BUSINESS_TYPE_PIC_CAMERA, wait) {
val picUpExtraInfo = TransferRequest.PicUpExtraInfo()
picUpExtraInfo.mIsRaw = false
picUpExtraInfo.mUinType = FileMsg.UIN_TROOP
it.mPicSendSource = 8
it.delayShowProgressTimeInMs = 2000
it.mExtraObj = picUpExtraInfo
it.mRec = record
}
}
operator fun get(contactType: ContactType, resourceType: ResourceType): suspend TransTarget.(Resource) -> Boolean {
return (ROUTE[contactType] ?: error("unsupported contact type: $contactType"))[resourceType]
?: error("Unsupported resource type: $resourceType")
}
}
internal suspend infix fun TransferTaskBuilder.trans(res: Resource): Boolean {
return Transfer[contact.type, res.type](contact, res)
}
internal class TransferTaskBuilder {
lateinit var contact: TransTarget
}
internal infix fun Transfer.with(contact: TransTarget): TransferTaskBuilder {
return TransferTaskBuilder().also {
it.contact = contact
}
}

View File

@ -0,0 +1,21 @@
package qq.service.contact
import com.tencent.qqnt.kernelpublic.nativeinterface.Contact
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import io.kritor.common.Scene
suspend fun Contact.longPeer(): Long {
return when(this.chatType) {
MsgConstant.KCHATTYPEGROUP -> peerUid.toLong()
MsgConstant.KCHATTYPETEMPC2CFROMGROUP, MsgConstant.KCHATTYPEC2C -> if (peerUid.startsWith("u_")) ContactHelper.getUinByUidAsync(peerUid).toLong() else peerUid.toLong()
else -> 0L
}
}
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()
else -> 0L
}
}

View File

@ -7,11 +7,16 @@ import com.tencent.mobileqq.profilecard.api.IProfileProtocolConst.PARAM_SELF_UIN
import com.tencent.mobileqq.profilecard.api.IProfileProtocolConst.PARAM_TARGET_UIN import com.tencent.mobileqq.profilecard.api.IProfileProtocolConst.PARAM_TARGET_UIN
import com.tencent.mobileqq.profilecard.api.IProfileProtocolService import com.tencent.mobileqq.profilecard.api.IProfileProtocolService
import com.tencent.mobileqq.profilecard.observer.ProfileCardObserver import com.tencent.mobileqq.profilecard.observer.ProfileCardObserver
import com.tencent.protofile.join_group_link.join_group_link
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import moe.fuqiuluo.shamrock.tools.decodeToOidb
import moe.fuqiuluo.shamrock.tools.slice
import qq.service.internals.NTServiceFetcher import qq.service.internals.NTServiceFetcher
import qq.service.QQInterfaces import qq.service.QQInterfaces
import tencent.im.oidb.cmd0x11b2.oidb_0x11b2
import tencent.im.oidb.oidb_sso
import kotlin.coroutines.resume import kotlin.coroutines.resume
internal object ContactHelper: QQInterfaces() { internal object ContactHelper: QQInterfaces() {
@ -177,4 +182,30 @@ internal object ContactHelper: QQInterfaces() {
} }
}[peerId]!! }[peerId]!!
} }
suspend fun getSharePrivateArkMsg(peerId: Long): String {
val reqBody = oidb_0x11b2.BusinessCardV3Req()
reqBody.uin.set(peerId)
reqBody.jump_url.set("mqqapi://card/show_pslcard?src_type=internal&source=sharecard&version=1&uin=$peerId")
val fromServiceMsg = sendOidbAW("OidbSvcTrpcTcp.0x11ca_0", 4790, 0, reqBody.toByteArray())
?: error("unable to fetch contact ark_json_text")
val body = fromServiceMsg.decodeToOidb()
val rsp = oidb_0x11b2.BusinessCardV3Rsp()
rsp.mergeFrom(body.bytes_bodybuffer.get().toByteArray())
return rsp.signed_ark_msg.get()
}
suspend fun getShareTroopArkMsg(groupId: Long): String {
val reqBody = join_group_link.ReqBody()
reqBody.get_ark.set(true)
reqBody.type.set(1)
reqBody.group_code.set(groupId)
val fromServiceMsg = sendBufferAW("GroupSvc.JoinGroupLink", true, reqBody.toByteArray())
?: error("unable to fetch contact ark_json_text")
val body = join_group_link.RspBody()
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
return body.signed_ark.get().toStringUtf8()
}
} }

View File

@ -1,23 +1,15 @@
@file:OptIn(ExperimentalStdlibApi::class)
package qq.service.file package qq.service.file
import com.tencent.mobileqq.pb.ByteStringMicro import com.tencent.mobileqq.pb.ByteStringMicro
import io.grpc.Status import io.grpc.Status
import io.grpc.StatusRuntimeException import io.grpc.StatusRuntimeException
import io.kritor.file.File import io.kritor.file.*
import io.kritor.file.Folder
import io.kritor.file.GetFileSystemInfoResponse
import io.kritor.file.GetFilesRequest
import io.kritor.file.GetFilesResponse
import io.kritor.file.folder
import io.kritor.file.getFileSystemInfoResponse
import io.kritor.file.getFilesRequest
import io.kritor.file.getFilesResponse
import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY
import moe.fuqiuluo.shamrock.tools.decodeToOidb
import moe.fuqiuluo.shamrock.tools.slice import moe.fuqiuluo.shamrock.tools.slice
import moe.fuqiuluo.shamrock.tools.toHexString
import moe.fuqiuluo.shamrock.utils.DeflateTools import moe.fuqiuluo.shamrock.utils.DeflateTools
import qq.service.QQInterfaces import qq.service.QQInterfaces
import tencent.im.oidb.cmd0x6d8.oidb_0x6d8 import tencent.im.oidb.cmd0x6d8.oidb_0x6d8
@ -39,12 +31,9 @@ internal object GroupFileHelper: QQInterfaces() {
val fileCnt: Int val fileCnt: Int
val limitCnt: Int val limitCnt: Int
if (fromServiceMsg.wupBuffer != null) { if (fromServiceMsg.wupBuffer != null) {
oidb_0x6d8.RspBody().mergeFrom( val oidb1 = fromServiceMsg.decodeToOidb()
oidb_sso.OIDBSSOPkg()
.mergeFrom(fromServiceMsg.wupBuffer.slice(4)) oidb_0x6d8.RspBody().mergeFrom(oidb1.bytes_bodybuffer.get().toByteArray()).group_file_cnt_rsp.apply {
.bytes_bodybuffer.get()
.toByteArray()
).group_file_cnt_rsp.apply {
fileCnt = uint32_all_file_count.get() fileCnt = uint32_all_file_count.get()
limitCnt = uint32_limit_count.get() limitCnt = uint32_limit_count.get()
} }
@ -61,11 +50,9 @@ internal object GroupFileHelper: QQInterfaces() {
val totalSpace: Long val totalSpace: Long
val usedSpace: Long val usedSpace: Long
if (fromServiceMsg2.isSuccess && fromServiceMsg2.wupBuffer != null) { if (fromServiceMsg2.isSuccess && fromServiceMsg2.wupBuffer != null) {
oidb_0x6d8.RspBody().mergeFrom( val oidb2 = fromServiceMsg2.decodeToOidb()
oidb_sso.OIDBSSOPkg()
.mergeFrom(fromServiceMsg2.wupBuffer.slice(4)) oidb_0x6d8.RspBody().mergeFrom(oidb2.bytes_bodybuffer.get().toByteArray()).group_space_rsp.apply {
.bytes_bodybuffer.get()
.toByteArray()).group_space_rsp.apply {
totalSpace = uint64_total_space.get() totalSpace = uint64_total_space.get()
usedSpace = uint64_used_space.get() usedSpace = uint64_used_space.get()
} }
@ -73,15 +60,15 @@ internal object GroupFileHelper: QQInterfaces() {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to fetch oidb response x2")) throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to fetch oidb response x2"))
} }
return getFileSystemInfoResponse { return GetFileSystemInfoResponse.newBuilder().apply {
this.fileCount = fileCnt this.fileCount = fileCnt
this.totalCount = limitCnt this.totalCount = limitCnt
this.totalSpace = totalSpace.toInt() this.totalSpace = totalSpace.toInt()
this.usedSpace = usedSpace.toInt() this.usedSpace = usedSpace.toInt()
} }.build()
} }
suspend fun getGroupFiles(groupId: Long, folderId: String = "/"): GetFilesResponse { suspend fun getGroupFiles(groupId: Long, folderId: String = "/"): GetFileListResponse {
val fileSystemInfo = getGroupFileSystemInfo(groupId) val fileSystemInfo = getGroupFileSystemInfo(groupId)
val fromServiceMsg = sendOidbAW("OidbSvc.0x6d8_1", 1752, 1, oidb_0x6d8.ReqBody().also { val fromServiceMsg = sendOidbAW("OidbSvc.0x6d8_1", 1752, 1, oidb_0x6d8.ReqBody().also {
it.file_list_info_req.set(oidb_0x6d8.GetFileListReqBody().apply { it.file_list_info_req.set(oidb_0x6d8.GetFileListReqBody().apply {
@ -103,48 +90,46 @@ internal object GroupFileHelper: QQInterfaces() {
uint32_show_onlinedoc_folder.set(0) uint32_show_onlinedoc_folder.set(0)
}) })
}.toByteArray(), timeout = 15.seconds) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request")) }.toByteArray(), timeout = 30.seconds) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
if (fromServiceMsg.wupBuffer == null) { if (fromServiceMsg.wupBuffer == null) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed")) throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
} }
val files = arrayListOf<File>() val files = arrayListOf<File>()
val dirs = arrayListOf<Folder>() val folders = arrayListOf<Folder>()
if (fromServiceMsg.wupBuffer != null) { if (fromServiceMsg.wupBuffer != null) {
val oidb = oidb_sso.OIDBSSOPkg().mergeFrom(fromServiceMsg.wupBuffer.slice(4).let { val oidb = fromServiceMsg.decodeToOidb()
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
})
oidb_0x6d8.RspBody().mergeFrom(oidb.bytes_bodybuffer.get().toByteArray()) oidb_0x6d8.RspBody().mergeFrom(oidb.bytes_bodybuffer.get().toByteArray())
.file_list_info_rsp.apply { .file_list_info_rsp.apply {
rpt_item_list.get().forEach { file -> rpt_item_list.get().forEach { file ->
if (file.uint32_type.get() == oidb_0x6d8.GetFileListRspBody.TYPE_FILE) { if (file.uint32_type.get() == oidb_0x6d8.GetFileListRspBody.TYPE_FILE) {
val fileInfo = file.file_info val fileInfo = file.file_info
files.add(io.kritor.file.file { files.add(File.newBuilder().apply {
this.fileId = fileInfo.str_file_id.get() this.fileId = fileInfo.str_file_id.get()
this.fileName = fileInfo.str_file_name.get() this.fileName = fileInfo.str_file_name.get()
this.fileSize = fileInfo.uint64_file_size.get() this.fileSize = fileInfo.uint64_file_size.get()
this.busId = fileInfo.uint32_bus_id.get() this.busId = fileInfo.uint32_bus_id.get()
this.uploadTime = fileInfo.uint32_upload_time.get() this.uploadTime = fileInfo.uint32_upload_time.get().toLong()
this.deadTime = fileInfo.uint32_dead_time.get() this.expireTime = fileInfo.uint32_dead_time.get().toLong()
this.modifyTime = fileInfo.uint32_modify_time.get() this.modifyTime = fileInfo.uint32_modify_time.get().toLong()
this.downloadTimes = fileInfo.uint32_download_times.get() this.downloadTimes = fileInfo.uint32_download_times.get()
this.uploader = fileInfo.uint64_uploader_uin.get() this.uploader = fileInfo.uint64_uploader_uin.get()
this.uploaderName = fileInfo.str_uploader_name.get() this.uploaderName = fileInfo.str_uploader_name.get()
this.sha = fileInfo.bytes_sha.get().toByteArray().toHexString() this.sha = fileInfo.bytes_sha.get().toByteArray().toHexString()
this.sha3 = fileInfo.bytes_sha3.get().toByteArray().toHexString() this.sha3 = fileInfo.bytes_sha3.get().toByteArray().toHexString()
this.md5 = fileInfo.bytes_md5.get().toByteArray().toHexString() this.md5 = fileInfo.bytes_md5.get().toByteArray().toHexString()
}) }.build())
} }
else if (file.uint32_type.get() == oidb_0x6d8.GetFileListRspBody.TYPE_FOLDER) { else if (file.uint32_type.get() == oidb_0x6d8.GetFileListRspBody.TYPE_FOLDER) {
val folderInfo = file.folder_info val folderInfo = file.folder_info
dirs.add(folder { folders.add(Folder.newBuilder().apply {
this.folderId = folderInfo.str_folder_id.get() this.folderId = folderInfo.str_folder_id.get()
this.folderName = folderInfo.str_folder_name.get() this.folderName = folderInfo.str_folder_name.get()
this.totalFileCount = folderInfo.uint32_total_file_count.get() this.totalFileCount = folderInfo.uint32_total_file_count.get()
this.createTime = folderInfo.uint32_create_time.get() this.createTime = folderInfo.uint32_create_time.get().toLong()
this.creator = folderInfo.uint64_create_uin.get() this.creator = folderInfo.uint64_create_uin.get()
this.creatorName = folderInfo.str_creator_name.get() this.creatorName = folderInfo.str_creator_name.get()
}) }.build())
} else { } else {
LogCenter.log("未知文件类型: ${file.uint32_type.get()}", Level.WARN) LogCenter.log("未知文件类型: ${file.uint32_type.get()}", Level.WARN)
} }
@ -154,9 +139,9 @@ internal object GroupFileHelper: QQInterfaces() {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to fetch oidb response")) throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to fetch oidb response"))
} }
return getFilesResponse { return GetFileListResponse.newBuilder().apply {
this.files.addAll(files) this.addAllFiles(files)
this.folders.addAll(folders) this.addAllFolders(folders)
} }.build()
} }
} }

View File

@ -19,10 +19,13 @@ import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.tools.decodeToOidb
import qq.service.internals.NTServiceFetcher import qq.service.internals.NTServiceFetcher
import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
import moe.fuqiuluo.shamrock.tools.putBuf32Long import moe.fuqiuluo.shamrock.tools.putBuf32Long
import moe.fuqiuluo.shamrock.tools.slice import moe.fuqiuluo.shamrock.tools.slice
import moe.fuqiuluo.shamrock.utils.PlatformUtils
import moe.fuqiuluo.shamrock.utils.PlatformUtils.QQ_9_0_65_VER
import protobuf.auto.toByteArray import protobuf.auto.toByteArray
import protobuf.oidb.cmd0xf16.Oidb0xf16 import protobuf.oidb.cmd0xf16.Oidb0xf16
import protobuf.oidb.cmd0xf16.SetGroupRemarkReq import protobuf.oidb.cmd0xf16.SetGroupRemarkReq
@ -42,6 +45,7 @@ import java.lang.reflect.Method
import java.lang.reflect.Modifier import java.lang.reflect.Modifier
import java.nio.ByteBuffer import java.nio.ByteBuffer
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.time.Duration.Companion.seconds
internal object GroupHelper: QQInterfaces() { internal object GroupHelper: QQInterfaces() {
private val RefreshTroopMemberInfoLock by lazy { Mutex() } private val RefreshTroopMemberInfoLock by lazy { Mutex() }
@ -99,6 +103,61 @@ internal object GroupHelper: QQInterfaces() {
return Result.success(troopList) return Result.success(troopList)
} }
suspend fun getTroopMemberInfoByUinV2(
groupId: String,
uin: String,
refresh: Boolean = false
): Result<TroopMemberInfo> {
val service = app.getRuntimeService(ITroopMemberInfoService::class.java, "all")
var info = service.getTroopMember(groupId, uin)
if (refresh || !service.isMemberInCache(groupId, uin) || info == null || info.troopnick == null) {
info = requestTroopMemberInfo(service, groupId, uin, timeout = 2000).getOrNull()
}
if (info == null) {
info = getTroopMemberInfoByUinViaNt(groupId, uin, timeout = 2000L).getOrNull()?.let {
TroopMemberInfo().apply {
troopnick = it.cardName
friendnick = it.nick
}
}
}
try {
if (info != null && (info.alias == null || info.alias.isBlank())) {
val req = group_member_info.ReqBody()
req.uint64_group_code.set(groupId.toLong())
req.uint64_uin.set(uin.toLong())
req.bool_new_client.set(true)
req.uint32_client_type.set(1)
req.uint32_rich_card_name_ver.set(1)
val fromServiceMsg = sendBufferAW("group_member_card.get_group_member_card_info", true, req.toByteArray(), timeout = 2.seconds)
if (fromServiceMsg != null && fromServiceMsg.wupBuffer != null) {
val rsp = group_member_info.RspBody()
rsp.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
if (rsp.msg_meminfo.str_location.has()) {
info.alias = rsp.msg_meminfo.str_location.get().toStringUtf8()
}
if (rsp.msg_meminfo.uint32_age.has()) {
info.age = rsp.msg_meminfo.uint32_age.get().toByte()
}
if (rsp.msg_meminfo.bytes_group_honor.has()) {
val honorBytes = rsp.msg_meminfo.bytes_group_honor.get().toByteArray()
val honor = troop_honor.GroupUserCardHonor()
honor.mergeFrom(honorBytes)
info.level = honor.level.get()
// 10315: medal_id not real group level
}
}
}
} catch (err: Throwable) {
LogCenter.log(err.stackTraceToString(), Level.WARN)
}
return if (info != null) {
Result.success(info)
} else {
Result.failure(Exception("获取群成员信息失败"))
}
}
private suspend fun requestGroupInfo( private suspend fun requestGroupInfo(
service: ITroopInfoService service: ITroopInfoService
): Boolean { ): Boolean {
@ -318,12 +377,12 @@ internal object GroupHelper: QQInterfaces() {
} }
} }
suspend fun setGroupUniqueTitle(groupId: Long, userId: Long, title: String) { suspend fun setGroupUniqueTitle(groupId: String, userId: String, title: String) {
val localMemberInfo = getTroopMemberInfoByUin(groupId, userId, true).getOrThrow() val localMemberInfo = getTroopMemberInfoByUin(groupId, userId, true).getOrThrow()
val req = Oidb_0x8fc.ReqBody() val req = Oidb_0x8fc.ReqBody()
req.uint64_group_code.set(groupId) req.uint64_group_code.set(groupId.toLong())
val memberInfo = Oidb_0x8fc.MemberInfo() val memberInfo = Oidb_0x8fc.MemberInfo()
memberInfo.uint64_uin.set(userId) memberInfo.uint64_uin.set(userId.toLong())
memberInfo.bytes_uin_name.set(ByteStringMicro.copyFromUtf8(localMemberInfo.troopnick.ifEmpty { memberInfo.bytes_uin_name.set(ByteStringMicro.copyFromUtf8(localMemberInfo.troopnick.ifEmpty {
localMemberInfo.troopremark.ifNullOrEmpty { "" } localMemberInfo.troopremark.ifNullOrEmpty { "" }
})) }))
@ -342,22 +401,46 @@ internal object GroupHelper: QQInterfaces() {
sendOidb("OidbSvc.0x89a_0", 2202, 0, reqBody.toByteArray()) sendOidb("OidbSvc.0x89a_0", 2202, 0, reqBody.toByteArray())
} }
suspend fun getGroupMemberList(groupId: String, refresh: Boolean): Result<List<TroopMemberInfo>> { suspend fun getGroupMemberList(groupId: Long, refresh: Boolean): Result<HashMap<String, MemberInfo>> {
val service = app.getRuntimeService(ITroopMemberInfoService::class.java, "all") val kernelService = NTServiceFetcher.kernelService
var memberList = service.getAllTroopMembers(groupId) val sessionService = kernelService.wrapperSession
if (refresh || memberList == null) { val service = sessionService.groupService
memberList = requestTroopMemberInfo(service, groupId).onFailure { val uids = suspendCancellableCoroutine { continuation ->
return Result.failure(Exception("获取群成员列表失败")) service.getAllMemberList(groupId, refresh) { _, _, groupMemberListResult ->
}.getOrThrow() continuation.resume(groupMemberListResult?.ids?.map {
it.uid
})
} }
}
val memberMap = suspendCancellableCoroutine { continuation ->
service.getMemberInfoForMqq(groupId, ArrayList(uids ?: emptyList()), refresh) { _, _, groupMemberListResult ->
continuation.resume(groupMemberListResult.infos)
}
}
// val extInfo = suspendCancellableCoroutine { continuation ->
// service.getMemberExtInfo(GroupMemberExtReq().apply {
// this.groupCode = groupId
// this.beginUin = 0.toString()
// this.groupType = ""
// this.memberExtFilter = MemberExtInfoFilter().apply {
// this.memberLevelInfoName = 1
// this.memberLevelInfoUin = 1
// this.nickName = 1
// this.specialTitle = 1
// this.memberLevelInfoActiveDay = 1
// }
// this.richCardNameVer = "1"
// this.sourceType = 1
// this.uinList = ArrayList(memberMap.values.toList().map {
// it.uin
// })
// }) { _, _, groupMemberExtListResult ->
// continuation.resume(groupMemberExtListResult)
// }
// }
getGroupInfo(groupId, true).onSuccess { return Result.success(memberMap)
if(it.wMemberNum > memberList.size) {
return getGroupMemberList(groupId, true)
}
}
return Result.success(memberList)
} }
suspend fun getProhibitedMemberList(groupId: Long): Result<List<ProhibitedMemberInfo>> { suspend fun getProhibitedMemberList(groupId: Long): Result<List<ProhibitedMemberInfo>> {
@ -373,8 +456,7 @@ internal object GroupHelper: QQInterfaces() {
if (fromServiceMsg.wupBuffer == null) { if (fromServiceMsg.wupBuffer == null) {
return Result.failure(RuntimeException("[oidb] failed")) return Result.failure(RuntimeException("[oidb] failed"))
} }
val body = oidb_sso.OIDBSSOPkg() val body = fromServiceMsg.decodeToOidb()
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
if(body.uint32_result.get() != 0) { if(body.uint32_result.get() != 0) {
return Result.failure(RuntimeException(body.str_error_msg.get())) return Result.failure(RuntimeException(body.str_error_msg.get()))
} }
@ -395,8 +477,7 @@ internal object GroupHelper: QQInterfaces() {
if (fromServiceMsg.wupBuffer == null) { if (fromServiceMsg.wupBuffer == null) {
return Result.failure(RuntimeException("[oidb] failed")) return Result.failure(RuntimeException("[oidb] failed"))
} }
val body = oidb_sso.OIDBSSOPkg() val body = fromServiceMsg.decodeToOidb()
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
if(body.uint32_result.get() != 0) { if(body.uint32_result.get() != 0) {
return Result.failure(RuntimeException(body.str_error_msg.get())) return Result.failure(RuntimeException(body.str_error_msg.get()))
} }
@ -468,13 +549,13 @@ internal object GroupHelper: QQInterfaces() {
} }
suspend fun getTroopMemberInfoByUin( suspend fun getTroopMemberInfoByUin(
groupId: Long, groupId: String,
uin: Long, uin: String,
refresh: Boolean = false refresh: Boolean = false
): Result<TroopMemberInfo> { ): Result<TroopMemberInfo> {
val service = app.getRuntimeService(ITroopMemberInfoService::class.java, "all") val service = app.getRuntimeService(ITroopMemberInfoService::class.java, "all")
var info = service.getTroopMember(groupId.toString(), uin.toString()) var info = service.getTroopMember(groupId, uin)
if (refresh || !service.isMemberInCache(groupId.toString(), uin.toString()) || info == null || info.troopnick == null) { if (refresh || !service.isMemberInCache(groupId, uin) || info == null || info.troopnick == null) {
info = requestTroopMemberInfo(service, groupId, uin).getOrNull() info = requestTroopMemberInfo(service, groupId, uin).getOrNull()
} }
if (info == null) { if (info == null) {
@ -488,8 +569,8 @@ internal object GroupHelper: QQInterfaces() {
try { try {
if (info != null && (info.alias == null || info.alias.isBlank())) { if (info != null && (info.alias == null || info.alias.isBlank())) {
val req = group_member_info.ReqBody() val req = group_member_info.ReqBody()
req.uint64_group_code.set(groupId) req.uint64_group_code.set(groupId.toLong())
req.uint64_uin.set(uin) req.uint64_uin.set(uin.toLong())
req.bool_new_client.set(true) req.bool_new_client.set(true)
req.uint32_client_type.set(1) req.uint32_client_type.set(1)
req.uint32_rich_card_name_ver.set(1) req.uint32_rich_card_name_ver.set(1)
@ -523,8 +604,8 @@ internal object GroupHelper: QQInterfaces() {
} }
suspend fun getTroopMemberInfoByUinViaNt( suspend fun getTroopMemberInfoByUinViaNt(
groupId: Long, groupId: String,
qq: Long, qq: String,
timeout: Long = 5000L timeout: Long = 5000L
): Result<MemberInfo> { ): Result<MemberInfo> {
return runCatching { return runCatching {
@ -533,13 +614,13 @@ internal object GroupHelper: QQInterfaces() {
val groupService = sessionService.groupService val groupService = sessionService.groupService
val info = withTimeoutOrNull(timeout) { val info = withTimeoutOrNull(timeout) {
suspendCancellableCoroutine { suspendCancellableCoroutine {
groupService.getTransferableMemberInfo(groupId) { code, _, data -> groupService.getTransferableMemberInfo(groupId.toLong()) { code, _, data ->
if (code != 0) { if (code != 0) {
it.resume(null) it.resume(null)
return@getTransferableMemberInfo return@getTransferableMemberInfo
} }
data.forEach { (_, info) -> data.forEach { (_, info) ->
if (info.uin == qq) { if (info.uin == qq.toLong()) {
it.resume(info) it.resume(info)
return@forEach return@forEach
} }
@ -556,21 +637,21 @@ internal object GroupHelper: QQInterfaces() {
} }
} }
private suspend fun requestTroopMemberInfo(service: ITroopMemberInfoService, groupId: Long, memberUin: Long, timeout: Long = 10_000): Result<TroopMemberInfo> { private suspend fun requestTroopMemberInfo(service: ITroopMemberInfoService, groupId: String, memberUin: String, timeout: Long = 10_000): Result<TroopMemberInfo> {
if(PlatformUtils.getQQVersionCode() >= QQ_9_0_65_VER) {
return Result.failure(Exception("当前版本不支持该API"))
}
val info = RefreshTroopMemberInfoLock.withLock { val info = RefreshTroopMemberInfoLock.withLock {
val groupIdStr = groupId.toString() service.deleteTroopMember(groupId, memberUin)
val memberUinStr = memberUin.toString()
service.deleteTroopMember(groupIdStr, memberUinStr)
requestMemberInfoV2(groupId, memberUin) requestMemberInfoV2(groupId, memberUin)
requestMemberInfo(groupId, memberUin) requestMemberInfo(groupId, memberUin)
withTimeoutOrNull(timeout) { withTimeoutOrNull(timeout) {
while (!service.isMemberInCache(groupIdStr, memberUinStr)) { while (!service.isMemberInCache(groupId, memberUin)) {
delay(200) delay(200)
} }
return@withTimeoutOrNull service.getTroopMember(groupIdStr, memberUinStr) return@withTimeoutOrNull service.getTroopMember(groupId, memberUin)
} }
} }
return if (info != null) { return if (info != null) {
@ -580,7 +661,7 @@ internal object GroupHelper: QQInterfaces() {
} }
} }
private fun requestMemberInfo(groupId: Long, memberUin: Long) { private fun requestMemberInfo(groupId: String, memberUin: String) {
val businessHandler = app.getBusinessHandler(BusinessHandlerFactory.TROOP_MEMBER_CARD_HANDLER) val businessHandler = app.getBusinessHandler(BusinessHandlerFactory.TROOP_MEMBER_CARD_HANDLER)
if (!::METHOD_REQ_MEMBER_INFO.isInitialized) { if (!::METHOD_REQ_MEMBER_INFO.isInitialized) {
@ -592,10 +673,10 @@ internal object GroupHelper: QQInterfaces() {
} }
} }
METHOD_REQ_MEMBER_INFO.invoke(businessHandler, groupId, memberUin) METHOD_REQ_MEMBER_INFO.invoke(businessHandler, groupId.toLong(), memberUin.toLong())
} }
private fun requestMemberInfoV2(groupId: Long, memberUin: Long) { private fun requestMemberInfoV2(groupId: String, memberUin: String) {
val businessHandler = app.getBusinessHandler(BusinessHandlerFactory.TROOP_MEMBER_CARD_HANDLER) val businessHandler = app.getBusinessHandler(BusinessHandlerFactory.TROOP_MEMBER_CARD_HANDLER)
if (!::METHOD_REQ_MEMBER_INFO_V2.isInitialized) { if (!::METHOD_REQ_MEMBER_INFO_V2.isInitialized) {
@ -607,7 +688,8 @@ internal object GroupHelper: QQInterfaces() {
} }
} }
METHOD_REQ_MEMBER_INFO_V2.invoke(businessHandler, groupId.toString(), groupUin2GroupCode(groupId).toString(), arrayListOf(memberUin.toString())) METHOD_REQ_MEMBER_INFO_V2.invoke(businessHandler,
groupId, groupUin2GroupCode(groupId.toLong()).toString(), arrayListOf(memberUin))
} }
private suspend fun requestTroopMemberInfo(service: ITroopMemberInfoService, groupId: String): Result<List<TroopMemberInfo>> { private suspend fun requestTroopMemberInfo(service: ITroopMemberInfoService, groupId: String): Result<List<TroopMemberInfo>> {

View File

@ -3,18 +3,28 @@
package qq.service.internals package qq.service.internals
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
import com.tencent.qqnt.kernel.nativeinterface.TextElement
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import moe.fuqiuluo.shamrock.config.AliveReply
import moe.fuqiuluo.shamrock.config.get
import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.helper.db.ImageDB
import moe.fuqiuluo.shamrock.helper.db.ImageMapping
import moe.fuqiuluo.shamrock.internals.GlobalEventTransmitter import moe.fuqiuluo.shamrock.internals.GlobalEventTransmitter
import moe.fuqiuluo.shamrock.utils.PlatformUtils
import moe.fuqiuluo.shamrock.utils.PlatformUtils.QQ_9_0_8_VER
import qq.service.bdh.RichProtoSvc import qq.service.bdh.RichProtoSvc
import qq.service.file.GroupFileHelper
import qq.service.group.GroupHelper
import qq.service.kernel.SimpleKernelMsgListener import qq.service.kernel.SimpleKernelMsgListener
import qq.service.msg.MessageHelper import qq.service.msg.MessageHelper
object AioListener: SimpleKernelMsgListener() { object AioListener : SimpleKernelMsgListener() {
override fun onRecvMsg(records: ArrayList<MsgRecord>) { override fun onRecvMsg(records: ArrayList<MsgRecord>) {
records.forEach { records.forEach {
GlobalScope.launch { GlobalScope.launch {
@ -27,7 +37,83 @@ object AioListener: SimpleKernelMsgListener() {
} }
} }
private suspend fun debugTest(record: MsgRecord, text: String) {
if (record.chatType == MsgConstant.KCHATTYPEGROUP && text == ".shamrock.members") {
val contact = MessageHelper.generateContact(record)
GroupHelper.getGroupMemberList(record.peerUin, true).onSuccess {
MessageHelper.sendMessage(contact, arrayListOf(
MsgElement().apply {
elementType = MsgConstant.KELEMTYPETEXT
textElement = TextElement().apply {
content = "memberCount: ${it.size}"
}
}
), 3, MessageHelper.generateMsgId(record.chatType))
}.onFailure {
LogCenter.log("获取群成员列表失败: $it", Level.ERROR)
}
} else if (record.chatType == MsgConstant.KCHATTYPEGROUP && text == ".shamrock.root_files") {
val contact = MessageHelper.generateContact(record)
val files = GroupFileHelper.getGroupFiles(record.peerUin)
MessageHelper.sendMessage(contact, arrayListOf(
MsgElement().apply {
elementType = MsgConstant.KELEMTYPETEXT
textElement = TextElement().apply {
content = "foldersCount: ${files.foldersCount}\nfilesCount: ${files.filesCount}"
}
}
), 3, MessageHelper.generateMsgId(record.chatType))
} else if (record.chatType == MsgConstant.KCHATTYPEGROUP && text == ".shamrock.pic_url") {
val contact = MessageHelper.generateContact(record)
val pic = record.elements.filter {
it.elementType == MsgConstant.KELEMTYPEPIC
}.map {
val image = it.picElement
val md5 = (image.md5HexStr ?: image.fileName
.replace("{", "")
.replace("}", "")
.replace("-", "").split(".")[0])
.uppercase()
var storeId = 0
if (PlatformUtils.getQQVersionCode() > QQ_9_0_8_VER) {
storeId = image.storeID
}
val originalUrl = image.originImageUrl ?: ""
return@map RichProtoSvc.getTempPicDownloadUrl(record.chatType, originalUrl, md5, image, storeId)
}
MessageHelper.sendMessage(contact, arrayListOf(
MsgElement().apply {
elementType = MsgConstant.KELEMTYPETEXT
textElement = TextElement().apply {
content = "picUrl: \n${
pic.joinToString("\n")
}"
}
}
), 3, MessageHelper.generateMsgId(record.chatType))
}
}
private suspend fun onMsg(record: MsgRecord) { private suspend fun onMsg(record: MsgRecord) {
if (AliveReply.get()) {
val texts = record.elements.filter { it.elementType == MsgConstant.KELEMTYPETEXT }
val text = texts.joinToString { it.textElement.content }
if (texts.isNotEmpty() && text == "ping") {
val contact = MessageHelper.generateContact(record)
MessageHelper.sendMessage(contact, arrayListOf(
MsgElement().apply {
elementType = MsgConstant.KELEMTYPETEXT
textElement = TextElement().apply {
content = "pong"
}
}
), 3, MessageHelper.generateMsgId(record.chatType))
return
}
debugTest(record, text)
}
when (record.chatType) { when (record.chatType) {
MsgConstant.KCHATTYPEGROUP -> { MsgConstant.KCHATTYPEGROUP -> {
if (record.senderUin == 0L) return if (record.senderUin == 0L) return
@ -60,7 +146,12 @@ object AioListener: SimpleKernelMsgListener() {
LogCenter.log("私聊临时消息(private = ${record.senderUin}, groupId=$groupCode)") LogCenter.log("私聊临时消息(private = ${record.senderUin}, groupId=$groupCode)")
if (!GlobalEventTransmitter.MessageTransmitter.transTempMessage(record, record.elements, groupCode, fromNick) if (!GlobalEventTransmitter.MessageTransmitter.transTempMessage(
record,
record.elements,
groupCode,
fromNick
)
) { ) {
LogCenter.log("私聊临时消息推送失败 -> MessageTransmitter", Level.WARN) LogCenter.log("私聊临时消息推送失败 -> MessageTransmitter", Level.WARN)
} }
@ -92,7 +183,6 @@ object AioListener: SimpleKernelMsgListener() {
} }
private suspend fun onC2CFileMsg(record: MsgRecord) { private suspend fun onC2CFileMsg(record: MsgRecord) {
val userId = record.senderUin
val fileMsg = record.elements.firstOrNull { val fileMsg = record.elements.firstOrNull {
it.elementType == MsgConstant.KELEMTYPEFILE it.elementType == MsgConstant.KELEMTYPEFILE
}?.fileElement ?: kotlin.run { }?.fileElement ?: kotlin.run {
@ -108,7 +198,7 @@ object AioListener: SimpleKernelMsgListener() {
val url = RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId) val url = RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId)
if (!GlobalEventTransmitter.FileNoticeTransmitter 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) LogCenter.log("私聊文件消息推送失败 -> FileNoticeTransmitter", Level.WARN)
} }
@ -116,7 +206,6 @@ object AioListener: SimpleKernelMsgListener() {
private suspend fun onGroupFileMsg(record: MsgRecord) { private suspend fun onGroupFileMsg(record: MsgRecord) {
val groupId = record.peerUin val groupId = record.peerUin
val userId = record.senderUin
val fileMsg = record.elements.firstOrNull { val fileMsg = record.elements.firstOrNull {
it.elementType == MsgConstant.KELEMTYPEFILE it.elementType == MsgConstant.KELEMTYPEFILE
}?.fileElement ?: kotlin.run { }?.fileElement ?: kotlin.run {
@ -132,9 +221,15 @@ object AioListener: SimpleKernelMsgListener() {
val url = RichProtoSvc.getGroupFileDownUrl(record.peerUin, uuid, bizId) val url = RichProtoSvc.getGroupFileDownUrl(record.peerUin, uuid, bizId)
if (!GlobalEventTransmitter.FileNoticeTransmitter 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) LogCenter.log("群聊文件消息推送失败 -> FileNoticeTransmitter", Level.WARN)
} }
} }
@OptIn(ExperimentalStdlibApi::class)
override fun onRecvSysMsg(arrayList: ArrayList<Byte>?) {
LogCenter.log("onRecvSysMsg")
LogCenter.log(arrayList?.toByteArray()?.toHexString() ?: "")
}
} }

View File

@ -0,0 +1,14 @@
package qq.service.internals
import com.tencent.qqnt.kernel.nativeinterface.DevInfo
import com.tencent.qqnt.kernel.nativeinterface.KickedInfo
import qq.service.kernel.SimpleKernelMsgListener
import java.util.ArrayList
object LineDevListener: SimpleKernelMsgListener() {
override fun onKickedOffLine(kickedInfo: KickedInfo) {
}
override fun onLineDev(devs: ArrayList<DevInfo>) {
}
}

View File

@ -58,6 +58,7 @@ internal object NTServiceFetcher {
try { try {
LogCenter.log("Register MSG listener successfully.") LogCenter.log("Register MSG listener successfully.")
msgService.addMsgListener(AioListener) msgService.addMsgListener(AioListener)
msgService.addMsgListener(LineDevListener)
// 接口缺失 暂不使用 // 接口缺失 暂不使用
//groupService.addKernelGroupListener(GroupEventListener) //groupService.addKernelGroupListener(GroupEventListener)

View File

@ -5,9 +5,8 @@ import com.tencent.mobileqq.qroute.QRoute
import com.tencent.qphone.base.remote.FromServiceMsg import com.tencent.qphone.base.remote.FromServiceMsg
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.msg.api.IMsgService import com.tencent.qqnt.msg.api.IMsgService
import io.kritor.event.GroupApplyType import io.kritor.event.GroupMemberDecreasedNotice.GroupMemberDecreasedType
import io.kritor.event.GroupMemberDecreasedType import io.kritor.event.GroupMemberIncreasedNotice.GroupMemberIncreasedType
import io.kritor.event.GroupMemberIncreasedType
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -127,7 +126,7 @@ internal object PrimitiveListener {
LogCenter.log("私聊戳一戳: $operation $action $target $suffix") LogCenter.log("私聊戳一戳: $operation $action $target $suffix")
if (!GlobalEventTransmitter.PrivateNoticeTransmitter if (!GlobalEventTransmitter.PrivateNoticeTransmitter
.transPrivatePoke(msgTime, operation.toLong(), target.toLong(), action, suffix, actionImg) .transPrivatePoke(msgTime, operation.toLong(), action, suffix, actionImg)
) { ) {
LogCenter.log("私聊戳一戳推送失败!", Level.WARN) LogCenter.log("私聊戳一戳推送失败!", Level.WARN)
} }
@ -162,7 +161,7 @@ internal object PrimitiveListener {
} }
LogCenter.log("来自$applier 的好友申请:$msg ($source)") LogCenter.log("来自$applier 的好友申请:$msg ($source)")
if (!GlobalEventTransmitter.RequestTransmitter if (!GlobalEventTransmitter.RequestTransmitter
.transFriendApp(msgTime, applier, msg, flag) .transFriendApp(msgTime, applierUid, applier, msg, flag)
) { ) {
LogCenter.log("好友申请推送失败!", Level.WARN) LogCenter.log("好友申请推送失败!", Level.WARN)
} }
@ -217,8 +216,13 @@ internal object PrimitiveListener {
}.decodeProtobuf<GroupCommonTipsEvent>() }.decodeProtobuf<GroupCommonTipsEvent>()
} }
val groupId = event.groupCode.toLong() val groupId = event.groupCode.toLong()
if (event.uniqueTitleChangeDetail == null) {
return
}
val detail = event.uniqueTitleChangeDetail!!.first() val detail = event.uniqueTitleChangeDetail!!.first()
// todo 贴表情也走的 732 16 这里
//detail = if (detail[5] is ProtoList) { //detail = if (detail[5] is ProtoList) {
// (detail[5] as ProtoList).value[0] // (detail[5] as ProtoList).value[0]
//} else { //} else {
@ -321,8 +325,8 @@ internal object PrimitiveListener {
it.key to it.value it.key to it.value
} }
val target = params["uin_str2"] ?: params["mqq_uin"] ?: return val target = params["uin_str2"] ?: params["mqq_uin"] ?: ""
val operation = params["uin_str1"] ?: return val operator = params["uin_str1"] ?: ""
val suffix = params["suffix_str"] ?: "" val suffix = params["suffix_str"] ?: ""
val actionImg = params["action_img_url"] ?: "" val actionImg = params["action_img_url"] ?: ""
val action = params["alt_str1"] val action = params["alt_str1"]
@ -333,9 +337,9 @@ internal object PrimitiveListener {
when (detail.type) { when (detail.type) {
1061u -> { 1061u -> {
LogCenter.log("群戳一戳($groupId): $operation $action $target $suffix") LogCenter.log("群戳一戳($groupId): $operator $action $target $suffix")
if (!GlobalEventTransmitter.GroupNoticeTransmitter 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) LogCenter.log("群戳一戳推送失败!", Level.WARN)
} }
@ -397,7 +401,7 @@ internal object PrimitiveListener {
val targetUid = event.memberUid val targetUid = event.memberUid
val type = event.type val type = event.type
GroupHelper.getGroupMemberList(groupCode.toString(), true).onFailure { GroupHelper.getGroupMemberList(groupCode, true).onFailure {
LogCenter.log("新成员加入刷新群成员列表失败: $groupCode", Level.WARN) LogCenter.log("新成员加入刷新群成员列表失败: $groupCode", Level.WARN)
}.onSuccess { }.onSuccess {
LogCenter.log("新成员加入刷新群成员列表成功,群成员数量: ${it.size}", Level.INFO) LogCenter.log("新成员加入刷新群成员列表成功,群成员数量: ${it.size}", Level.INFO)
@ -434,7 +438,7 @@ internal object PrimitiveListener {
val type = event.type val type = event.type
val operatorUid = event.operatorUid val operatorUid = event.operatorUid
GroupHelper.getGroupMemberList(groupCode.toString(), true).onFailure { GroupHelper.getGroupMemberList(groupCode, true).onFailure {
LogCenter.log("新成员加入刷新群成员列表失败: $groupCode", Level.WARN) LogCenter.log("新成员加入刷新群成员列表失败: $groupCode", Level.WARN)
}.onSuccess { }.onSuccess {
LogCenter.log("新成员加入刷新群成员列表成功,群成员数量: ${it.size}", Level.INFO) LogCenter.log("新成员加入刷新群成员列表成功,群成员数量: ${it.size}", Level.INFO)
@ -507,7 +511,7 @@ internal object PrimitiveListener {
if (wholeBan) { if (wholeBan) {
LogCenter.log("群全员禁言($groupCode): $operator -> ${if (rawDuration != 0) "开启" else "关闭"}") LogCenter.log("群全员禁言($groupCode): $operator -> ${if (rawDuration != 0) "开启" else "关闭"}")
if (!GlobalEventTransmitter.GroupNoticeTransmitter if (!GlobalEventTransmitter.GroupNoticeTransmitter
.transGroupWholeBan(msgTime, groupCode, operator, rawDuration != 0) .transGroupWholeBan(msgTime, groupCode, operatorUid, operator, rawDuration != 0)
) { ) {
LogCenter.log("群禁言推送失败!", Level.WARN) LogCenter.log("群禁言推送失败!", Level.WARN)
} }
@ -595,7 +599,7 @@ internal object PrimitiveListener {
} }
LogCenter.log("入群申请($groupCode) $applier: \"$reason\", seq: $msgSeq") LogCenter.log("入群申请($groupCode) $applier: \"$reason\", seq: $msgSeq")
if (!GlobalEventTransmitter.RequestTransmitter if (!GlobalEventTransmitter.RequestTransmitter
.transGroupApply(time, applier, applierUid, reason, groupCode, flag, GroupApplyType.GROUP_APPLY_ADD) .transGroupApply(time, applier, applierUid, reason, groupCode, flag)
) { ) {
LogCenter.log("入群申请推送失败!", Level.WARN) LogCenter.log("入群申请推送失败!", Level.WARN)
} }
@ -630,7 +634,7 @@ internal object PrimitiveListener {
} }
LogCenter.log("邀请入群申请($groupCode): $applier") LogCenter.log("邀请入群申请($groupCode): $applier")
if (!GlobalEventTransmitter.RequestTransmitter if (!GlobalEventTransmitter.RequestTransmitter
.transGroupApply(time, applier, applierUid, "", groupCode, flag, GroupApplyType.GROUP_APPLY_ADD) .transGroupApply(time, applier, applierUid, "", groupCode, flag)
) { ) {
LogCenter.log("邀请入群申请推送失败!", Level.WARN) LogCenter.log("邀请入群申请推送失败!", Level.WARN)
} }
@ -658,7 +662,7 @@ internal object PrimitiveListener {
"$time;$groupCode;$uin" "$time;$groupCode;$uin"
} }
if (!GlobalEventTransmitter.RequestTransmitter if (!GlobalEventTransmitter.RequestTransmitter
.transGroupApply(time, invitor, invitorUid, "", groupCode, flag, GroupApplyType.GROUP_APPLY_INVITE) .transGroupInvite(time, invitorUid, invitor, groupCode, flag)
) { ) {
LogCenter.log("邀请入群推送失败!", Level.WARN) LogCenter.log("邀请入群推送失败!", Level.WARN)
} }

View File

@ -1,7 +1,7 @@
package qq.service.kernel package qq.service.kernel
import com.tencent.qqnt.kernel.nativeinterface.BroadcastHelperTransNotifyInfo import com.tencent.qqnt.kernel.nativeinterface.BroadcastHelperTransNotifyInfo
import com.tencent.qqnt.kernel.nativeinterface.Contact import com.tencent.qqnt.kernelpublic.nativeinterface.Contact
import com.tencent.qqnt.kernel.nativeinterface.ContactMsgBoxInfo import com.tencent.qqnt.kernel.nativeinterface.ContactMsgBoxInfo
import com.tencent.qqnt.kernel.nativeinterface.CustomWithdrawConfig import com.tencent.qqnt.kernel.nativeinterface.CustomWithdrawConfig
import com.tencent.qqnt.kernel.nativeinterface.DevInfo import com.tencent.qqnt.kernel.nativeinterface.DevInfo
@ -12,6 +12,7 @@ import com.tencent.qqnt.kernel.nativeinterface.FileTransNotifyInfo
import com.tencent.qqnt.kernel.nativeinterface.FirstViewDirectMsgNotifyInfo import com.tencent.qqnt.kernel.nativeinterface.FirstViewDirectMsgNotifyInfo
import com.tencent.qqnt.kernel.nativeinterface.FirstViewGroupGuildInfo import com.tencent.qqnt.kernel.nativeinterface.FirstViewGroupGuildInfo
import com.tencent.qqnt.kernel.nativeinterface.FreqLimitInfo import com.tencent.qqnt.kernel.nativeinterface.FreqLimitInfo
import com.tencent.qqnt.kernel.nativeinterface.GProGuildTopFeedMsg
import com.tencent.qqnt.kernel.nativeinterface.GroupFileListResult import com.tencent.qqnt.kernel.nativeinterface.GroupFileListResult
import com.tencent.qqnt.kernel.nativeinterface.GroupGuildNotifyInfo import com.tencent.qqnt.kernel.nativeinterface.GroupGuildNotifyInfo
import com.tencent.qqnt.kernel.nativeinterface.GroupItem import com.tencent.qqnt.kernel.nativeinterface.GroupItem
@ -27,6 +28,7 @@ import com.tencent.qqnt.kernel.nativeinterface.MsgAbstract
import com.tencent.qqnt.kernel.nativeinterface.MsgElement import com.tencent.qqnt.kernel.nativeinterface.MsgElement
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
import com.tencent.qqnt.kernel.nativeinterface.MsgSetting import com.tencent.qqnt.kernel.nativeinterface.MsgSetting
import com.tencent.qqnt.kernel.nativeinterface.QueryUserSecQualityRsp
import com.tencent.qqnt.kernel.nativeinterface.RecvdOrder import com.tencent.qqnt.kernel.nativeinterface.RecvdOrder
import com.tencent.qqnt.kernel.nativeinterface.RelatedWordEmojiInfo import com.tencent.qqnt.kernel.nativeinterface.RelatedWordEmojiInfo
import com.tencent.qqnt.kernel.nativeinterface.SearchGroupFileResult import com.tencent.qqnt.kernel.nativeinterface.SearchGroupFileResult
@ -133,6 +135,9 @@ abstract class SimpleKernelMsgListener: IKernelMsgListener {
override fun onGuildNotificationAbstractUpdate(guildNotificationAbstractInfo: GuildNotificationAbstractInfo?) { override fun onGuildNotificationAbstractUpdate(guildNotificationAbstractInfo: GuildNotificationAbstractInfo?) {
}
override fun onGuildTopFeedUpdate(gProGuildTopFeedMsg: GProGuildTopFeedMsg?) {
} }
override fun onHitCsRelatedEmojiResult(downloadRelateEmojiResultInfo: DownloadRelateEmojiResultInfo?) { override fun onHitCsRelatedEmojiResult(downloadRelateEmojiResultInfo: DownloadRelateEmojiResultInfo?) {
@ -155,11 +160,11 @@ abstract class SimpleKernelMsgListener: IKernelMsgListener {
} }
override fun onKickedOffLine(kickedInfo: KickedInfo?) { override fun onKickedOffLine(kickedInfo: KickedInfo) {
} }
override fun onLineDev(arrayList: ArrayList<DevInfo>?) { override fun onLineDev(devs: ArrayList<DevInfo>) {
} }
@ -205,6 +210,9 @@ abstract class SimpleKernelMsgListener: IKernelMsgListener {
override fun onMsgSettingUpdate(msgSetting: MsgSetting?) { override fun onMsgSettingUpdate(msgSetting: MsgSetting?) {
}
override fun onMsgWithRichLinkInfoUpdate(arrayList: ArrayList<MsgRecord>?) {
} }
override fun onNtFirstViewMsgSyncEnd() { override fun onNtFirstViewMsgSyncEnd() {
@ -256,6 +264,9 @@ abstract class SimpleKernelMsgListener: IKernelMsgListener {
override fun onRecvUDCFlag(i2: Int) { override fun onRecvUDCFlag(i2: Int) {
}
override fun onRedTouchChanged() {
} }
override fun onRichMediaDownloadComplete(fileTransNotifyInfo: FileTransNotifyInfo?) { override fun onRichMediaDownloadComplete(fileTransNotifyInfo: FileTransNotifyInfo?) {
@ -278,7 +289,13 @@ abstract class SimpleKernelMsgListener: IKernelMsgListener {
} }
override fun onSysMsgNotification(i2: Int, j2: Long, j3: Long, arrayList: ArrayList<Byte>?) { override fun onSysMsgNotification(
i2: Int,
j2: Long,
j3: Long,
z: Boolean,
arrayList: ArrayList<Byte>?
) {
} }
@ -300,6 +317,9 @@ abstract class SimpleKernelMsgListener: IKernelMsgListener {
override fun onUserOnlineStatusChanged(z: Boolean) { override fun onUserOnlineStatusChanged(z: Boolean) {
}
override fun onUserSecQualityChanged(queryUserSecQualityRsp: QueryUserSecQualityRsp?) {
} }
override fun onUserTabStatusChanged(arrayList: ArrayList<TabStatusInfo>?) { override fun onUserTabStatusChanged(arrayList: ArrayList<TabStatusInfo>?) {

View File

@ -0,0 +1,40 @@
package qq.service.lightapp
sealed class ArkAppInfo(
val appId: Long,
val version: String,
val packageName: String,
val signature: String,
val miniAppId: Long = 0,
val appName: String = ""
) {
data object QQMusic: ArkAppInfo(
appId = 100497308,
version = "0.0.0",
packageName = "com.tencent.qqmusic",
signature = "cbd27cd7c861227d013a25b2d10f0799"
)
data object NetEaseMusic: ArkAppInfo(
appId = 100495085,
version = "0.0.0",
packageName = "com.netease.cloudmusic",
signature = "da6b069da1e2982db3e386233f68d76d"
)
data object DanMaKu: ArkAppInfo(
appId = 100951776,
version = "0.0.0",
packageName = "tv.danmaku.bili",
signature = "7194d531cbe7960a22007b9f6bdaa38b",
miniAppId = 1109937557,
appName = "哔哩哔哩"
)
data object Docs: ArkAppInfo(
appId = 0,
version = "0.0.0",
packageName = "",
signature = "f3da3147654d9a21f3237b88f20dce9c",
miniAppId = 1108338344
)
}

View File

@ -0,0 +1,48 @@
package qq.service.lightapp
import com.tencent.qqnt.kernelpublic.nativeinterface.Contact
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import qq.service.QQInterfaces
import qq.service.contact.longPeer
import tencent.im.oidb.cmd0xb77.oidb_cmd0xb77
internal object ArkMsgHelper: QQInterfaces() {
suspend fun tryShareMusic(
contact: Contact,
msgId: Long,
arkAppInfo: ArkAppInfo,
title: String,
singer: String,
jumpUrl: String,
previewUrl: String,
musicUrl: String,
) {
val req = oidb_cmd0xb77.ReqBody()
req.appid.set(arkAppInfo.appId)
req.app_type.set(1)
req.msg_style.set(4)
req.client_info.set(oidb_cmd0xb77.ClientInfo().also {
it.platform.set(1)
it.sdk_version.set(arkAppInfo.version)
it.android_package_name.set(arkAppInfo.packageName)
it.android_signature.set(arkAppInfo.signature)
})
req.ext_info.set(oidb_cmd0xb77.ExtInfo().also {
it.msg_seq.set(msgId)
})
req.recv_uin.set(contact.longPeer())
req.rich_msg_body.set(oidb_cmd0xb77.RichMsgBody().also {
it.title.set(title)
it.summary.set(singer)
it.url.set(jumpUrl)
it.picture_url.set(previewUrl)
it.music_url.set(musicUrl)
})
when (contact.chatType) {
MsgConstant.KCHATTYPEGROUP -> req.send_type.set(1)
MsgConstant.KCHATTYPEC2C -> req.send_type.set(0)
else -> error("不支持该聊天类型发送音乐分享: chatType: ${contact.chatType}")
}
sendOidb("OidbSvc.0xb77_9", 0xb77, 9, req.toByteArray())
}
}

View File

@ -0,0 +1,58 @@
package qq.service.lightapp
import com.tencent.biz.map.trpcprotocol.LbsSendInfo
import com.tencent.proto.lbsshare.LBSShare
import com.tencent.qqnt.kernelpublic.nativeinterface.Contact
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import moe.fuqiuluo.shamrock.helper.IllegalParamsException
import moe.fuqiuluo.shamrock.tools.slice
import qq.service.QQInterfaces
import qq.service.contact.longPeer
import kotlin.math.roundToInt
internal object LbsHelper: QQInterfaces() {
suspend fun tryShareLocation(contact: Contact, lat: Double, lon: Double): Result<Unit> {
val req = LbsSendInfo.SendMessageReq()
req.uint64_peer_account.set(contact.longPeer())
when (contact.chatType) {
MsgConstant.KCHATTYPEGROUP -> req.enum_relation_type.set(1)
MsgConstant.KCHATTYPEC2C -> req.enum_relation_type.set(0)
else -> error("Not supported chat type: $contact")
}
req.str_name.set("位置分享")
req.str_address.set(getAddressWithLonLat(lat, lon).onFailure {
return Result.failure(it)
}.getOrNull())
req.str_lat.set(lat.toString())
req.str_lng.set(lon.toString())
sendBuffer("trpc.qq_lbs.qq_lbs_ark.LocationArk.SsoSendMessage", true, req.toByteArray())
return Result.success(Unit)
}
suspend fun getAddressWithLonLat(lat: Double, lon: Double): Result<String> {
if (lat > 90 || lat < 0) {
return Result.failure(IllegalParamsException("纬度大小错误"))
}
if (lon > 180 || lon < 0) {
return Result.failure(IllegalParamsException("经度大小错误"))
}
val latO = (lat * 1000000).roundToInt()
val lngO = (lon * 1000000).roundToInt()
val req = LBSShare.LocationReq()
req.lat.set(latO)
req.lng.set(lngO)
req.coordinate.set(1)
req.keyword.set("")
req.category.set("")
req.page.set(0)
req.count.set(20)
req.requireMyLbs.set(1)
req.imei.set("")
val fromServiceMsg = sendBufferAW("LbsShareSvr.location", true, req.toByteArray())
?: return Result.failure(Exception("获取位置失败"))
val resp = LBSShare.LocationResp()
resp.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
val location = resp.mylbs
return Result.success(location.addr.get())
}
}

View File

@ -0,0 +1,96 @@
package qq.service.lightapp
import com.tencent.qqnt.kernelpublic.nativeinterface.Contact
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import kotlinx.serialization.json.Json
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.tools.GlobalClient
import moe.fuqiuluo.shamrock.tools.asInt
import moe.fuqiuluo.shamrock.tools.asJsonArray
import moe.fuqiuluo.shamrock.tools.asJsonArrayOrNull
import moe.fuqiuluo.shamrock.tools.asJsonObject
import moe.fuqiuluo.shamrock.tools.asString
import moe.fuqiuluo.shamrock.tools.asStringOrNull
import moe.fuqiuluo.shamrock.utils.MD5
internal object MusicHelper {
suspend fun tryShare163MusicById(contact: Contact, msgId: Long, id: String): Boolean {
try {
val respond = GlobalClient.get("https://music.163.com/api/song/detail/?id=$id&ids=[$id]")
val songInfo = Json.parseToJsonElement(respond.bodyAsText()).asJsonObject["songs"].asJsonArray.first().asJsonObject
val name = songInfo["name"].asString
val title = songInfo["name"].asString
val singerName = songInfo["artists"].asJsonArray.first().asJsonObject["name"].asString
val previewUrl = songInfo["album"].asJsonObject["picUrl"].asString
val playUrl = "https://music.163.com/song/media/outer/url?id=$id.mp3"
val jumpUrl = "https://music.163.com/#/song?id=$id"
ArkMsgHelper.tryShareMusic(
contact,
msgId,
ArkAppInfo.NetEaseMusic,
title.ifBlank { name },
singerName,
jumpUrl,
previewUrl,
playUrl
)
return true
} catch (e: Throwable) {
LogCenter.log(e.stackTraceToString(), Level.ERROR)
}
return false
}
suspend fun tryShareQQMusicById(contact: Contact, msgId: Long, id: String): Boolean {
try {
val respond = GlobalClient.get("https://u.y.qq.com/cgi-bin/musicu.fcg?format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq.json&needNewCode=0&data={%22comm%22:{%22ct%22:24,%22cv%22:0},%22songinfo%22:{%22method%22:%22get_song_detail_yqq%22,%22param%22:{%22song_type%22:0,%22song_mid%22:%22%22,%22song_id%22:$id},%22module%22:%22music.pf_song_detail_svr%22}}")
val songInfo = Json.parseToJsonElement(respond.bodyAsText()).asJsonObject["songinfo"].asJsonObject
if (songInfo["code"].asInt != 0) {
LogCenter.log("获取QQ音乐($id)的歌曲信息失败。")
return false
} else {
val data = songInfo["data"].asJsonObject
val trackInfo = data["track_info"].asJsonObject
val mid = trackInfo["mid"].asString
val previewMid = trackInfo["album"].asJsonObject["mid"].asString
val singerMid = trackInfo["singer"].asJsonArrayOrNull?.let {
it[0].asJsonObject["mid"].asStringOrNull
} ?: ""
val name = trackInfo["name"].asString
val title = trackInfo["title"].asString
val singerName = trackInfo["singer"].asJsonArray.first().asJsonObject["name"].asString
val vs = trackInfo["vs"].asJsonArrayOrNull?.let {
it[0].asStringOrNull
} ?: ""
val code = MD5.getMd5Hex("${mid}q;z(&l~sdf2!nK".toByteArray()).substring(0 .. 4).uppercase()
val playUrl = "http://c6.y.qq.com/rsc/fcgi-bin/fcg_pyq_play.fcg?songid=&songmid=$mid&songtype=1&fromtag=50&uin=&code=$code"
val previewUrl = if (vs.isNotEmpty()) {
"http://y.gtimg.cn/music/photo_new/T062R150x150M000$vs}.jpg"
} else if (previewMid.isNotEmpty()) {
"http://y.gtimg.cn/music/photo_new/T002R150x150M000$previewMid.jpg"
} else if (singerMid.isNotEmpty()){
"http://y.gtimg.cn/music/photo_new/T001R150x150M000$singerMid.jpg"
} else {
""
}
val jumpUrl = "https://i.y.qq.com/v8/playsong.html?platform=11&appshare=android_qq&appversion=10030010&hosteuin=oKnlNenz7i-s7c**&songmid=${mid}&type=0&appsongtype=1&_wv=1&source=qq&ADTAG=qfshare"
ArkMsgHelper.tryShareMusic(
contact,
msgId,
ArkAppInfo.QQMusic,
title.ifBlank { name },
singerName,
jumpUrl,
previewUrl,
playUrl
)
return true
}
} catch (e: Throwable) {
LogCenter.log(e.stackTraceToString(), Level.ERROR)
}
return false
}
}

View File

@ -0,0 +1,10 @@
package qq.service.lightapp
import kotlinx.serialization.Serializable
@Serializable
internal data class Region(
val adcode: Int,
val province: String?,
val city: String?
)

View File

@ -0,0 +1,78 @@
package qq.service.lightapp
import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.client.request.url
import io.ktor.client.statement.bodyAsText
import io.ktor.http.HttpStatusCode
import io.ktor.http.encodeURLQueryComponent
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.tools.GlobalClient
import moe.fuqiuluo.shamrock.tools.GlobalJson
import moe.fuqiuluo.shamrock.tools.asInt
import moe.fuqiuluo.shamrock.tools.asJsonArray
import moe.fuqiuluo.shamrock.tools.asJsonObject
import moe.fuqiuluo.shamrock.tools.asStringOrNull
import qq.service.QQInterfaces
import qq.service.ticket.TicketHelper
internal object WeatherHelper: QQInterfaces() {
suspend fun fetchWeatherCard(code: Int): Result<JsonObject> {
val cookie = TicketHelper.getCookie("mp.qq.com")
val resp = GlobalClient.get("https://weather.mp.qq.com/page/poster?_wv=2&&_wwv=4&adcode=$code") {
header("Cookie", cookie)
}
if (resp.status != HttpStatusCode.OK) {
LogCenter.log("fetchWeatherCard: error: ${resp.status}, cookie: $cookie", Level.ERROR)
return Result.failure(Exception("search city failed"))
}
val textJson = resp.bodyAsText()
.replace("\n", "")
.split("window.__INITIAL_STATE__ =")[1]
.split("};")[0].trim() + "}"
//LogCenter.log(textJson)
return Result.success(Json.parseToJsonElement(textJson).asJsonObject)
}
suspend fun searchCity(query: String): Result<List<Region>> {
val pskey = TicketHelper.getPSKey(app.currentAccountUin, "mp.qq.com") ?: ""
val cookie = TicketHelper.getCookie("mp.qq.com")
val gtk = TicketHelper.getCSRF(pskey)
val resp = GlobalClient.get {
url("https://weather.mp.qq.com/trpc/weather/SearchRegions?g_tk=$gtk&key=${query.encodeURLQueryComponent()}&offset=0&count=25")
header("Cookie", cookie)
}
if (resp.status != HttpStatusCode.OK) {
LogCenter.log("GetWeatherCityCode: error: ${resp.status}, cookie: $cookie, bkn: $gtk", Level.ERROR)
return Result.failure(Exception("search city failed"))
}
val json = GlobalJson.parseToJsonElement(resp.bodyAsText()).asJsonObject
val cnt = json["totalCount"].asInt
if (cnt == 0) {
return Result.success(emptyList())
}
val regions = json["regions"].asJsonArray.map {
val region = it.asJsonObject
Region(
region["adcode"].asInt,
region["province"].asStringOrNull,
region["city"].asStringOrNull
)
}
return Result.success(regions)
}
}

Some files were not shown because too many files have changed in this diff Show More