mirror of
https://github.com/whitechi73/OpenShamrock.git
synced 2024-08-14 05:12:17 +00:00
Compare commits
6 Commits
5637db43be
...
1afc0ac6a6
Author | SHA1 | Date | |
---|---|---|---|
1afc0ac6a6 | |||
638bf72392 | |||
07364c8298 | |||
ee6e13a5bb | |||
c3934778c7 | |||
13a49dd70b |
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "kritor"]
|
||||||
|
path = kritor
|
||||||
|
url = https://github.com/KarinJS/kritor
|
@ -9,6 +9,6 @@ java {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(DEPENDENCY_PROTOBUF)
|
//implementation(DEPENDENCY_PROTOBUF)
|
||||||
implementation(kotlinx("serialization-protobuf", "1.6.2"))
|
implementation(kotlinx("serialization-protobuf", "1.6.2"))
|
||||||
}
|
}
|
7
annotations/src/main/java/kritor/service/Grpc.kt
Normal file
7
annotations/src/main/java/kritor/service/Grpc.kt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package kritor.service
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.FUNCTION)
|
||||||
|
annotation class Grpc(
|
||||||
|
val serviceName: String,
|
||||||
|
val funcName: String
|
||||||
|
)
|
@ -5,6 +5,8 @@ import kotlinx.serialization.protobuf.ProtoBuf
|
|||||||
|
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
val EMPTY_BYTE_ARRAY = ByteArray(0)
|
||||||
|
|
||||||
interface Protobuf<T: Protobuf<T>>
|
interface Protobuf<T: Protobuf<T>>
|
||||||
|
|
||||||
inline fun <reified T: Protobuf<T>> ByteArray.decodeProtobuf(to: KClass<T>? = null): T {
|
inline fun <reified T: Protobuf<T>> ByteArray.decodeProtobuf(to: KClass<T>? = null): T {
|
||||||
|
@ -18,12 +18,17 @@ internal object InitHandler: ModuleHandler() {
|
|||||||
|
|
||||||
val maps = hashMapOf<String, Any?>()
|
val maps = hashMapOf<String, Any?>()
|
||||||
|
|
||||||
RPCPort.update(context, maps)
|
|
||||||
RPCAddress.update(context, maps)
|
|
||||||
ForceTablet.update(context, maps)
|
|
||||||
ActiveRPC.update(context, maps)
|
ActiveRPC.update(context, maps)
|
||||||
|
AliveReply.update(context, maps)
|
||||||
|
AntiJvmTrace.update(context, maps)
|
||||||
|
DebugMode.update(context, maps)
|
||||||
|
EnableOldBDH.update(context, maps)
|
||||||
|
EnableSelfMessage.update(context, maps)
|
||||||
|
ForceTablet.update(context, maps)
|
||||||
PassiveRPC.update(context, maps)
|
PassiveRPC.update(context, maps)
|
||||||
ResourceGroup.update(context, maps)
|
ResourceGroup.update(context, maps)
|
||||||
|
RPCAddress.update(context, maps)
|
||||||
|
RPCPort.update(context, maps)
|
||||||
|
|
||||||
callback(context, 1, maps)
|
callback(context, 1, maps)
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,6 @@ val DEPENDENCY_ANDROIDX = arrayOf(
|
|||||||
"androidx.activity:activity-compose:1.7.2",
|
"androidx.activity:activity-compose:1.7.2",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
const val DEPENDENCY_PROTOBUF = "com.google.protobuf:protobuf-java:3.24.0"
|
|
||||||
|
|
||||||
fun room(name: String) = "androidx.room:room-$name:${Versions.roomVersion}"
|
fun room(name: String) = "androidx.room:room-$name:${Versions.roomVersion}"
|
||||||
|
|
||||||
fun kotlinx(name: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$name:$version"
|
fun kotlinx(name: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$name:$version"
|
||||||
|
1
kritor
Submodule
1
kritor
Submodule
Submodule kritor added at f4fa15754e
@ -15,7 +15,7 @@ dependencies {
|
|||||||
implementation("com.google.devtools.ksp:symbol-processing-api:1.9.21-1.0.15")
|
implementation("com.google.devtools.ksp:symbol-processing-api:1.9.21-1.0.15")
|
||||||
implementation("com.squareup:kotlinpoet:1.14.2")
|
implementation("com.squareup:kotlinpoet:1.14.2")
|
||||||
|
|
||||||
implementation(DEPENDENCY_PROTOBUF)
|
//implementation(DEPENDENCY_PROTOBUF)
|
||||||
implementation(kotlinx("serialization-protobuf", "1.6.2"))
|
implementation(kotlinx("serialization-protobuf", "1.6.2"))
|
||||||
|
|
||||||
ksp("dev.zacsweers.autoservice:auto-service-ksp:1.1.0")
|
ksp("dev.zacsweers.autoservice:auto-service-ksp:1.1.0")
|
||||||
|
@ -37,7 +37,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(DEPENDENCY_PROTOBUF)
|
//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"))
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
@file:OptIn(ExperimentalSerializationApi::class)
|
@file:OptIn(ExperimentalSerializationApi::class)
|
||||||
package protobuf.oidb.cmd0x11c5
|
package protobuf.oidb.cmd0x11c5
|
||||||
|
|
||||||
import com.google.protobuf.Internal.EMPTY_BYTE_ARRAY
|
import moe.fuqiuluo.symbols.EMPTY_BYTE_ARRAY
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.protobuf.ProtoNumber
|
import kotlinx.serialization.protobuf.ProtoNumber
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package protobuf.oidb.cmd0x388
|
package protobuf.oidb.cmd0x388
|
||||||
|
|
||||||
import com.google.protobuf.Internal.EMPTY_BYTE_ARRAY
|
import moe.fuqiuluo.symbols.EMPTY_BYTE_ARRAY
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.protobuf.ProtoNumber
|
import kotlinx.serialization.protobuf.ProtoNumber
|
||||||
import moe.fuqiuluo.symbols.Protobuf
|
import moe.fuqiuluo.symbols.Protobuf
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
package protobuf.oidb.cmd0x388
|
package protobuf.oidb.cmd0x388
|
||||||
|
|
||||||
import com.google.protobuf.Internal.EMPTY_BYTE_ARRAY
|
import moe.fuqiuluo.symbols.EMPTY_BYTE_ARRAY
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.protobuf.ProtoNumber
|
import kotlinx.serialization.protobuf.ProtoNumber
|
||||||
|
@ -39,3 +39,6 @@ include(
|
|||||||
include(":protobuf")
|
include(":protobuf")
|
||||||
include(":processor")
|
include(":processor")
|
||||||
include(":annotations")
|
include(":annotations")
|
||||||
|
include(":kritor")
|
||||||
|
|
||||||
|
project(":kritor").projectDir = file("kritor/protos")
|
@ -5,6 +5,7 @@ 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"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,6 +64,8 @@ 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(":protobuf"))
|
implementation(project(":protobuf"))
|
||||||
implementation(project(":annotations"))
|
implementation(project(":annotations"))
|
||||||
ksp(project(":processor"))
|
ksp(project(":processor"))
|
||||||
@ -72,7 +75,7 @@ dependencies {
|
|||||||
DEPENDENCY_ANDROIDX.forEach {
|
DEPENDENCY_ANDROIDX.forEach {
|
||||||
implementation(it)
|
implementation(it)
|
||||||
}
|
}
|
||||||
implementation(DEPENDENCY_PROTOBUF)
|
//implementation(DEPENDENCY_PROTOBUF)
|
||||||
|
|
||||||
implementation(room("runtime"))
|
implementation(room("runtime"))
|
||||||
kapt(room("compiler"))
|
kapt(room("compiler"))
|
||||||
@ -84,7 +87,12 @@ dependencies {
|
|||||||
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(ktor("network", "tls-certificates"))
|
|
||||||
|
implementation("io.grpc:grpc-stub:1.62.2")
|
||||||
|
implementation("io.grpc:grpc-protobuf-lite:1.62.2")
|
||||||
|
implementation("com.google.protobuf:protobuf-kotlin-lite:3.25.3")
|
||||||
|
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")
|
||||||
@ -92,3 +100,46 @@ dependencies {
|
|||||||
androidTestImplementation(platform("androidx.compose:compose-bom:2023.06.01"))
|
androidTestImplementation(platform("androidx.compose:compose-bom:2023.06.01"))
|
||||||
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
|
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.withType<KotlinCompile>().all {
|
||||||
|
kotlinOptions {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
43
xposed/src/main/assets/config.properties
Normal file
43
xposed/src/main/assets/config.properties
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Shamrock Config
|
||||||
|
|
||||||
|
# 资源上传群组
|
||||||
|
resource_group=883536416
|
||||||
|
|
||||||
|
# 强制使用平板模式
|
||||||
|
force_tablet=false
|
||||||
|
|
||||||
|
# 被动(反向)RPC开关
|
||||||
|
passive_rpc=false
|
||||||
|
# 被动(反向)RPC地址
|
||||||
|
rpc_address=
|
||||||
|
# 第一个被动RPC鉴权token
|
||||||
|
rpc_address.ticket=
|
||||||
|
# 如果有多个请使用
|
||||||
|
# 我是第二个地址
|
||||||
|
#rpc_address.1=
|
||||||
|
# 第二个被动RPC鉴权token
|
||||||
|
#rpc_address.1.ticket=
|
||||||
|
|
||||||
|
# 主动(正向)RPC开关
|
||||||
|
active_rpc=false
|
||||||
|
# 主动(正向)RPC端口
|
||||||
|
rpc_port=5700
|
||||||
|
# 主动RPC鉴权token
|
||||||
|
active_ticket=
|
||||||
|
# 多鉴权token支持
|
||||||
|
# 第二个主动RPC鉴权token
|
||||||
|
#active_ticket.1=
|
||||||
|
|
||||||
|
# 自回复开关
|
||||||
|
#alive_reply=false
|
||||||
|
# 自回复消息
|
||||||
|
enable_self_message=false
|
||||||
|
|
||||||
|
# 旧BDH兼容开关
|
||||||
|
enable_old_bdh=false
|
||||||
|
|
||||||
|
# 反JVM调用栈跟踪
|
||||||
|
anti_jvm_trace=true
|
||||||
|
|
||||||
|
# 调试模式
|
||||||
|
#debug=false
|
33
xposed/src/main/java/kritor/server/KritorServer.kt
Normal file
33
xposed/src/main/java/kritor/server/KritorServer.kt
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
package kritor.server
|
||||||
|
|
||||||
|
import io.grpc.Grpc
|
||||||
|
import io.grpc.InsecureServerCredentials
|
||||||
|
import io.grpc.ServerBuilder
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kritor.service.*
|
||||||
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
class KritorServer(
|
||||||
|
private val port: Int
|
||||||
|
): CoroutineScope {
|
||||||
|
private val server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create())
|
||||||
|
.addService(Authentication)
|
||||||
|
.build()!!
|
||||||
|
|
||||||
|
fun start(block: Boolean = false) {
|
||||||
|
LogCenter.log("KritorServer started at port $port.")
|
||||||
|
server.start()
|
||||||
|
|
||||||
|
if (block) {
|
||||||
|
server.awaitTermination()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val coroutineContext: CoroutineContext =
|
||||||
|
Dispatchers.IO.limitedParallelism(12)
|
||||||
|
}
|
49
xposed/src/main/java/kritor/service/Authentication.kt
Normal file
49
xposed/src/main/java/kritor/service/Authentication.kt
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package kritor.service
|
||||||
|
|
||||||
|
import io.kritor.AuthCode
|
||||||
|
import io.kritor.AuthReq
|
||||||
|
import io.kritor.AuthRsp
|
||||||
|
import io.kritor.AuthenticationGrpcKt
|
||||||
|
import io.kritor.authRsp
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package moe.fuqiuluo.shamrock.config
|
||||||
|
|
||||||
|
object ActiveTicket: ConfigKey<String>() {
|
||||||
|
override fun name(): String = "active_ticket"
|
||||||
|
|
||||||
|
override fun default(): String = ""
|
||||||
|
}
|
@ -10,3 +10,6 @@ abstract class ConfigKey<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal inline fun <reified Type, reified T: ConfigKey<Type>> T.get(): Type {
|
||||||
|
return ShamrockConfig[this]
|
||||||
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package moe.fuqiuluo.shamrock.config
|
package moe.fuqiuluo.shamrock.config
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader.moduleLoader
|
||||||
import mqq.app.MobileQQ
|
import mqq.app.MobileQQ
|
||||||
import java.util.Properties
|
import java.util.Properties
|
||||||
|
|
||||||
@ -12,15 +14,28 @@ private val configFile = configDir.resolve("config.prop")
|
|||||||
|
|
||||||
private val configKeys = setOf(
|
private val configKeys = setOf(
|
||||||
ActiveRPC,
|
ActiveRPC,
|
||||||
|
AntiJvmTrace,
|
||||||
ForceTablet,
|
ForceTablet,
|
||||||
PassiveRPC,
|
PassiveRPC,
|
||||||
ResourceGroup,
|
ResourceGroup,
|
||||||
RPCAddress,
|
RPCAddress,
|
||||||
RPCPort
|
RPCPort,
|
||||||
)
|
)
|
||||||
|
|
||||||
internal object ShamrockConfig: Properties() {
|
internal object ShamrockConfig: Properties() {
|
||||||
init {
|
init {
|
||||||
|
if (!configFile.exists()) {
|
||||||
|
moduleLoader.getResourceAsStream("assets/config.properties")?.use {
|
||||||
|
configDir.resolve("default.prop").outputStream().use { output ->
|
||||||
|
it.copyTo(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
moduleLoader.getResourceAsStream("assets/config.properties")?.use {
|
||||||
|
configFile.outputStream().use { output ->
|
||||||
|
it.copyTo(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (configFile.exists()) configFile.inputStream().use {
|
if (configFile.exists()) configFile.inputStream().use {
|
||||||
load(it)
|
load(it)
|
||||||
}
|
}
|
||||||
@ -39,13 +54,34 @@ internal object ShamrockConfig: Properties() {
|
|||||||
fun updateConfig(intent: Intent? = null) {
|
fun updateConfig(intent: Intent? = null) {
|
||||||
intent?.let {
|
intent?.let {
|
||||||
for (key in configKeys) {
|
for (key in configKeys) {
|
||||||
|
when (key.default()) {
|
||||||
|
is String -> {
|
||||||
val value = intent.getStringExtra(key.name())
|
val value = intent.getStringExtra(key.name())
|
||||||
if (value != null) setProperty(key.name(), value)
|
if (value != null) setProperty(key.name(), value)
|
||||||
}
|
}
|
||||||
|
is Boolean -> {
|
||||||
|
val value = intent.getBooleanExtra(key.name(), key.default() as Boolean)
|
||||||
|
setProperty(key.name(), value.toString())
|
||||||
|
}
|
||||||
|
is Int -> {
|
||||||
|
val value = intent.getIntExtra(key.name(), key.default() as Int)
|
||||||
|
setProperty(key.name(), value.toString())
|
||||||
|
}
|
||||||
|
is Long -> {
|
||||||
|
val value = intent.getLongExtra(key.name(), key.default() as Long)
|
||||||
|
setProperty(key.name(), value.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getProperty(ActiveTicket.name()).isNullOrEmpty()) {
|
||||||
|
setProperty(ActiveTicket.name(), "") // 初始化ticket
|
||||||
|
}
|
||||||
|
|
||||||
setProperty(IsInit.name(), "true")
|
setProperty(IsInit.name(), "true")
|
||||||
}
|
}
|
||||||
configFile.outputStream().use {
|
configFile.outputStream().use {
|
||||||
store(it, "Shamrock Config ${System.currentTimeMillis()}")
|
store(it, "Shamrock Config")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import moe.fuqiuluo.shamrock.helper.Level
|
|||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
import moe.fuqiuluo.shamrock.tools.hookMethod
|
import moe.fuqiuluo.shamrock.tools.hookMethod
|
||||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
||||||
|
import qq.service.internals.AioListener
|
||||||
import qq.service.internals.msgService
|
import qq.service.internals.msgService
|
||||||
|
|
||||||
internal object NTServiceFetcher {
|
internal object NTServiceFetcher {
|
||||||
@ -58,7 +59,7 @@ internal object NTServiceFetcher {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
LogCenter.log("Register MSG listener successfully.")
|
LogCenter.log("Register MSG listener successfully.")
|
||||||
//msgService.addMsgListener(AioListener)
|
msgService.addMsgListener(AioListener)
|
||||||
|
|
||||||
// 接口缺失 暂不使用
|
// 接口缺失 暂不使用
|
||||||
//groupService.addKernelGroupListener(GroupEventListener)
|
//groupService.addKernelGroupListener(GroupEventListener)
|
||||||
|
@ -4,12 +4,38 @@ package moe.fuqiuluo.shamrock.xposed.actions
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kritor.server.KritorServer
|
||||||
|
import moe.fuqiuluo.shamrock.config.ActiveRPC
|
||||||
|
import moe.fuqiuluo.shamrock.config.RPCPort
|
||||||
|
import moe.fuqiuluo.shamrock.config.ShamrockConfig
|
||||||
|
import moe.fuqiuluo.shamrock.config.get
|
||||||
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
import moe.fuqiuluo.symbols.Process
|
import moe.fuqiuluo.symbols.Process
|
||||||
import moe.fuqiuluo.symbols.XposedHook
|
import moe.fuqiuluo.symbols.XposedHook
|
||||||
|
|
||||||
|
private lateinit var server: KritorServer
|
||||||
|
|
||||||
@XposedHook(Process.MAIN, priority = 10)
|
@XposedHook(Process.MAIN, priority = 10)
|
||||||
internal class InitRemoteService : IAction {
|
internal class InitRemoteService : IAction {
|
||||||
override fun invoke(ctx: Context) {
|
override fun invoke(ctx: Context) {
|
||||||
|
GlobalScope.launch {
|
||||||
|
runCatching {
|
||||||
|
if (ActiveRPC.get()) {
|
||||||
|
if (!::server.isInitialized) {
|
||||||
|
server = KritorServer(RPCPort.get())
|
||||||
|
server.start()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LogCenter.log("ActiveRPC is disabled, KritorServer will not be started.")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}.onFailure {
|
||||||
|
LogCenter.log("Start RPC failed: ${it.message}", Level.ERROR)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,8 +34,6 @@ internal object NativeLoader {
|
|||||||
XposedBridge.log("[Shamrock] 反射检测到 Android x86")
|
XposedBridge.log("[Shamrock] 反射检测到 Android x86")
|
||||||
true
|
true
|
||||||
} else false
|
} else false
|
||||||
}.onFailure {
|
|
||||||
XposedBridge.log("[Shamrock] ${it.stackTraceToString()}")
|
|
||||||
}.getOrElse { false }
|
}.getOrElse { false }
|
||||||
|
|
||||||
private fun getLibFilePath(name: String): String {
|
private fun getLibFilePath(name: String): String {
|
||||||
|
314
xposed/src/main/java/qq/service/internals/AioListener.kt
Normal file
314
xposed/src/main/java/qq/service/internals/AioListener.kt
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
package qq.service.internals
|
||||||
|
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.BroadcastHelperTransNotifyInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.Contact
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.ContactMsgBoxInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.CustomWithdrawConfig
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.DevInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.DownloadRelateEmojiResultInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.EmojiNotifyInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.EmojiResourceInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.FileTransNotifyInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.FirstViewDirectMsgNotifyInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.FirstViewGroupGuildInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.FreqLimitInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.GroupFileListResult
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.GroupGuildNotifyInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.GroupItem
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.GuildInteractiveNotificationItem
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.GuildMsgAbFlag
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.GuildNotificationAbstractInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.HitRelatedEmojiWordsResult
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.IKernelMsgListener
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.ImportOldDbMsgNotifyInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.InputStatusInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.KickedInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgAbstract
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgSetting
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.RecvdOrder
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.RelatedWordEmojiInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.SearchGroupFileResult
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.TabStatusInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.TempChatInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.UnreadCntInfo
|
||||||
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
|
|
||||||
|
object AioListener: IKernelMsgListener {
|
||||||
|
override fun onRecvMsg(arrayList: ArrayList<MsgRecord>?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMsgRecall(chatType: Int, peerId: String, msgId: Long) {
|
||||||
|
LogCenter.log("onMsgRecall($chatType, $peerId, $msgId)")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAddSendMsg(record: MsgRecord) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMsgInfoListUpdate(msgList: ArrayList<MsgRecord>?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTempChatInfoUpdate(tempChatInfo: TempChatInfo) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMsgAbstractUpdate(arrayList: ArrayList<MsgAbstract>?) {
|
||||||
|
//arrayList?.forEach {
|
||||||
|
// LogCenter.log("onMsgAbstractUpdate($it)", Level.WARN)
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRecvMsgSvrRspTransInfo(
|
||||||
|
j2: Long,
|
||||||
|
contact: Contact?,
|
||||||
|
i2: Int,
|
||||||
|
i3: Int,
|
||||||
|
str: String?,
|
||||||
|
bArr: ByteArray?
|
||||||
|
) {
|
||||||
|
LogCenter.log("onRecvMsgSvrRspTransInfo($j2, $contact, $i2, $i3, $str)", Level.DEBUG)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRecvS2CMsg(arrayList: ArrayList<Byte>?) {
|
||||||
|
LogCenter.log("onRecvS2CMsg(${arrayList.toString()})", Level.DEBUG)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRecvSysMsg(arrayList: ArrayList<Byte>?) {
|
||||||
|
LogCenter.log("onRecvSysMsg(${arrayList.toString()})", Level.DEBUG)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBroadcastHelperDownloadComplete(broadcastHelperTransNotifyInfo: BroadcastHelperTransNotifyInfo?) {}
|
||||||
|
|
||||||
|
override fun onBroadcastHelperProgerssUpdate(broadcastHelperTransNotifyInfo: BroadcastHelperTransNotifyInfo?) {}
|
||||||
|
|
||||||
|
override fun onChannelFreqLimitInfoUpdate(
|
||||||
|
contact: Contact?,
|
||||||
|
z: Boolean,
|
||||||
|
freqLimitInfo: FreqLimitInfo?
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onContactUnreadCntUpdate(unreadMap: HashMap<Int, HashMap<String, UnreadCntInfo>>) {
|
||||||
|
// 推送未读消息数量
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCustomWithdrawConfigUpdate(customWithdrawConfig: CustomWithdrawConfig?) {
|
||||||
|
LogCenter.log("onCustomWithdrawConfigUpdate: " + customWithdrawConfig.toString(), Level.DEBUG)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDraftUpdate(contact: Contact?, arrayList: ArrayList<MsgElement>?, j2: Long) {
|
||||||
|
LogCenter.log("onDraftUpdate: " + contact.toString() + "|" + arrayList + "|" + j2.toString(), Level.DEBUG)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEmojiDownloadComplete(emojiNotifyInfo: EmojiNotifyInfo?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEmojiResourceUpdate(emojiResourceInfo: EmojiResourceInfo?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFeedEventUpdate(firstViewDirectMsgNotifyInfo: FirstViewDirectMsgNotifyInfo?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFileMsgCome(arrayList: ArrayList<MsgRecord>?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFirstViewDirectMsgUpdate(firstViewDirectMsgNotifyInfo: FirstViewDirectMsgNotifyInfo?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFirstViewGroupGuildMapping(arrayList: ArrayList<FirstViewGroupGuildInfo>?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGrabPasswordRedBag(
|
||||||
|
i2: Int,
|
||||||
|
str: String?,
|
||||||
|
i3: Int,
|
||||||
|
recvdOrder: RecvdOrder?,
|
||||||
|
msgRecord: MsgRecord?
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onKickedOffLine(kickedInfo: KickedInfo?) {
|
||||||
|
LogCenter.log("onKickedOffLine($kickedInfo)")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRichMediaUploadComplete(notifyInfo: FileTransNotifyInfo) {
|
||||||
|
LogCenter.log({ "[BDH] 资源上传完成(${notifyInfo.trasferStatus}, ${notifyInfo.fileId}, ${notifyInfo.msgId}, ${notifyInfo.commonFileInfo})" }, Level.DEBUG)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRecvOnlineFileMsg(arrayList: ArrayList<MsgRecord>?) {
|
||||||
|
LogCenter.log(("onRecvOnlineFileMsg" + arrayList?.joinToString { ", " }), Level.DEBUG)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRichMediaDownloadComplete(fileTransNotifyInfo: FileTransNotifyInfo) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRichMediaProgerssUpdate(fileTransNotifyInfo: FileTransNotifyInfo) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSearchGroupFileInfoUpdate(searchGroupFileResult: SearchGroupFileResult?) {
|
||||||
|
LogCenter.log("onSearchGroupFileInfoUpdate($searchGroupFileResult)", Level.DEBUG)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGroupFileInfoAdd(groupItem: GroupItem?) {
|
||||||
|
LogCenter.log("onGroupFileInfoAdd: " + groupItem.toString(), Level.DEBUG)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGroupFileInfoUpdate(groupFileListResult: GroupFileListResult?) {
|
||||||
|
LogCenter.log("onGroupFileInfoUpdate: " + groupFileListResult.toString(), Level.DEBUG)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGroupGuildUpdate(groupGuildNotifyInfo: GroupGuildNotifyInfo?) {
|
||||||
|
LogCenter.log("onGroupGuildUpdate: " + groupGuildNotifyInfo.toString(), Level.DEBUG)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGroupTransferInfoAdd(groupItem: GroupItem?) {
|
||||||
|
LogCenter.log("onGroupTransferInfoAdd: " + groupItem.toString(), Level.DEBUG)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGroupTransferInfoUpdate(groupFileListResult: GroupFileListResult?) {
|
||||||
|
LogCenter.log("onGroupTransferInfoUpdate: " + groupFileListResult.toString(), Level.DEBUG)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGuildInteractiveUpdate(guildInteractiveNotificationItem: GuildInteractiveNotificationItem?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGuildMsgAbFlagChanged(guildMsgAbFlag: GuildMsgAbFlag?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGuildNotificationAbstractUpdate(guildNotificationAbstractInfo: GuildNotificationAbstractInfo?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onHitCsRelatedEmojiResult(downloadRelateEmojiResultInfo: DownloadRelateEmojiResultInfo?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onHitEmojiKeywordResult(hitRelatedEmojiWordsResult: HitRelatedEmojiWordsResult?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onHitRelatedEmojiResult(relatedWordEmojiInfo: RelatedWordEmojiInfo?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onImportOldDbProgressUpdate(importOldDbMsgNotifyInfo: ImportOldDbMsgNotifyInfo?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onInputStatusPush(inputStatusInfo: InputStatusInfo?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLineDev(devList: ArrayList<DevInfo>?) {
|
||||||
|
//LogCenter.log("onLineDev($arrayList)")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLogLevelChanged(newLevel: Long) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMsgBoxChanged(arrayList: ArrayList<ContactMsgBoxInfo>?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMsgDelete(contact: Contact?, arrayList: ArrayList<Long>?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMsgEventListUpdate(hashMap: HashMap<String, ArrayList<Long>>?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMsgInfoListAdd(arrayList: ArrayList<MsgRecord>?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMsgQRCodeStatusChanged(i2: Int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMsgSecurityNotify(msgRecord: MsgRecord?) {
|
||||||
|
LogCenter.log("onMsgSecurityNotify($msgRecord)")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMsgSettingUpdate(msgSetting: MsgSetting?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNtFirstViewMsgSyncEnd() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNtMsgSyncEnd() {
|
||||||
|
LogCenter.log("NTKernel同步消息完成", Level.DEBUG)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNtMsgSyncStart() {
|
||||||
|
LogCenter.log("NTKernel同步消息开始", Level.DEBUG)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReadFeedEventUpdate(firstViewDirectMsgNotifyInfo: FirstViewDirectMsgNotifyInfo?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRecvGroupGuildFlag(i2: Int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRecvUDCFlag(i2: Int) {
|
||||||
|
LogCenter.log("onRecvUDCFlag($i2)", Level.DEBUG)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSendMsgError(j2: Long, contact: Contact?, i2: Int, str: String?) {
|
||||||
|
LogCenter.log("onSendMsgError($j2, $contact, $j2, $str)", Level.DEBUG)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSysMsgNotification(i2: Int, j2: Long, j3: Long, arrayList: ArrayList<Byte>?) {
|
||||||
|
LogCenter.log("onSysMsgNotification($i2, $j2, $j3, $arrayList)", Level.DEBUG)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUnreadCntAfterFirstView(hashMap: HashMap<Int, ArrayList<UnreadCntInfo>>?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUnreadCntUpdate(hashMap: HashMap<Int, ArrayList<UnreadCntInfo>>?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUserChannelTabStatusChanged(z: Boolean) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUserOnlineStatusChanged(z: Boolean) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUserTabStatusChanged(arrayList: ArrayList<TabStatusInfo>?) {
|
||||||
|
LogCenter.log("onUserTabStatusChanged($arrayList)", Level.DEBUG)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onlineStatusBigIconDownloadPush(i2: Int, j2: Long, str: String?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onlineStatusSmallIconDownloadPush(i2: Int, j2: Long, str: String?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,8 @@ import com.tencent.qphone.base.remote.FromServiceMsg
|
|||||||
import com.tencent.qphone.base.remote.ToServiceMsg
|
import com.tencent.qphone.base.remote.ToServiceMsg
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
|
|
||||||
typealias MsfPush = (FromServiceMsg) -> Unit
|
typealias MsfPush = (FromServiceMsg) -> Unit
|
||||||
typealias MsfResp = (ToServiceMsg, FromServiceMsg) -> Unit
|
typealias MsfResp = (ToServiceMsg, FromServiceMsg) -> Unit
|
||||||
@ -45,8 +47,13 @@ internal object MSFHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onResp(toServiceMsg: ToServiceMsg, fromServiceMsg: FromServiceMsg) {
|
fun onResp(toServiceMsg: ToServiceMsg, fromServiceMsg: FromServiceMsg) {
|
||||||
val cmd = toServiceMsg.getAttribute("respkey") as Int
|
runCatching {
|
||||||
|
val cmd = toServiceMsg.getAttribute("shamrock_uid") as? Int?
|
||||||
|
?: return@runCatching
|
||||||
val resp = mRespHandler[cmd]
|
val resp = mRespHandler[cmd]
|
||||||
resp?.invoke(toServiceMsg, fromServiceMsg)
|
resp?.invoke(toServiceMsg, fromServiceMsg)
|
||||||
|
}.onFailure {
|
||||||
|
LogCenter.log("MSF.onResp failed: ${it.message}", Level.ERROR)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user