mirror of
https://github.com/whitechi73/OpenShamrock.git
synced 2024-08-14 05:12:17 +00:00
Compare commits
119 Commits
4f1d19fcbd
...
1.0.9
Author | SHA1 | Date | |
---|---|---|---|
59d762eecf | |||
e891bc8512 | |||
494c70f2f8 | |||
7baf459b2a | |||
36a09ca088 | |||
926c4659f6 | |||
cb7c68f36c | |||
72af39208c | |||
042f4bd330 | |||
9aef71b09f | |||
9cbe755520 | |||
df02f9f872 | |||
5cbb695a66 | |||
c014e85faa | |||
4a396b0935 | |||
d59fcf9f6a | |||
cdc664f44a | |||
ad313f384c | |||
b6a510ce05 | |||
bed5947909 | |||
fb6578d243 | |||
d33cace7aa | |||
659d4e5da4 | |||
ac2aee8c0e | |||
0faada7b5a | |||
680317da13 | |||
7782feb6ac | |||
d66358a1f3 | |||
824f280b3a | |||
6936262d62 | |||
0955267ee5 | |||
f3da62fa74 | |||
abbac6315c | |||
0cf10eabd6 | |||
8c33267887 | |||
f030104ff2 | |||
ee5fcc3403 | |||
5e819179b4 | |||
ea206faf4f | |||
5adfc544a2 | |||
bdb75841cf | |||
a3dc0d06b2 | |||
3664352f23 | |||
2770979fee | |||
6c9b282c6a | |||
3a07116093 | |||
be58c368e9 | |||
a95d8d85e8 | |||
1d035fa378 | |||
7d0b60271e | |||
d38777d06a | |||
7bacea3288 | |||
ca47f9dbdf | |||
cb4268edef | |||
c16f9d543c | |||
93c49953cf | |||
883e949cc1 | |||
a528030cbb | |||
bbdfb7c04e | |||
1afc0ac6a6 | |||
638bf72392 | |||
07364c8298 | |||
ee6e13a5bb | |||
c3934778c7 | |||
13a49dd70b | |||
5637db43be | |||
69cdbad643 | |||
a06708bf95 | |||
2ac0003166 | |||
d92d1daffb | |||
27f837adbe | |||
661680e60b | |||
54b7eb95a8 | |||
265fff3cd2 | |||
8ca0a3815a | |||
da6d34c53e | |||
61ffb37951 | |||
593f461ffe | |||
12d594697d | |||
352aa5f737 | |||
9546e90bec | |||
26b4d95ad8 | |||
4a6109fbe6 | |||
d6142173c9 | |||
38cf806b40 | |||
82269bb171 | |||
fc0d7a62af | |||
60fdfd9071 | |||
1f620bcc06 | |||
737acfa41b | |||
3619cba33c | |||
52ec43abf8 | |||
e96c356de4 | |||
bbdb0a65fb | |||
ec56e32be1 | |||
4dc83fdeba | |||
541422a43e | |||
cb7bf00e17 | |||
a3171b3111 | |||
02626489eb | |||
a9a2e9a3dd | |||
964c55de31 | |||
befb4a2bef | |||
210609bd7b | |||
3e03d4782d | |||
675a7a5321 | |||
a78b5cab23 | |||
252a3527a8 | |||
ea4cf06edf | |||
1424efd7f8 | |||
eb807a0332 | |||
e9a3a82b68 | |||
fca66f3259 | |||
92ebe0c6a8 | |||
6b1147d065 | |||
720313124c | |||
68ea62ea0b | |||
5584a41af0 | |||
0bca46bba3 |
2
.github/workflows/build-apk.yml
vendored
2
.github/workflows/build-apk.yml
vendored
@ -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
|
||||||
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "kritor"]
|
||||||
|
path = kritor/kritor
|
||||||
|
url = https://github.com/KarinJS/kritor
|
23
README.md
23
README.md
@ -16,20 +16,35 @@
|
|||||||
|
|
||||||
## 简介
|
## 简介
|
||||||
|
|
||||||
☘ 基于 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/)!
|
|
||||||
|
|
||||||
## 兼容|迁移|替代 说明
|
## 兼容|迁移|替代 说明
|
||||||
|
|
||||||
- 一键移植:本项目基于 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>
|
||||||
|
|
||||||
## 权限声明
|
## 权限声明
|
||||||
|
|
||||||
|
@ -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"))
|
||||||
}
|
}
|
9
annotations/src/main/java/kritor/service/Grpc.kt
Normal file
9
annotations/src/main/java/kritor/service/Grpc.kt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package kritor.service
|
||||||
|
|
||||||
|
@Retention(AnnotationRetention.SOURCE)
|
||||||
|
@Target(AnnotationTarget.FUNCTION)
|
||||||
|
annotation class Grpc(
|
||||||
|
val serviceName: String,
|
||||||
|
val funcName: String,
|
||||||
|
|
||||||
|
)
|
@ -1,8 +0,0 @@
|
|||||||
package moe.fuqiuluo.symbols
|
|
||||||
|
|
||||||
@Retention(AnnotationRetention.SOURCE)
|
|
||||||
@Target(AnnotationTarget.CLASS)
|
|
||||||
annotation class OneBotHandler(
|
|
||||||
val actionName: String,
|
|
||||||
val alias: Array<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 {
|
||||||
|
@ -4,7 +4,7 @@ import java.io.ByteArrayOutputStream
|
|||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("org.jetbrains.kotlin.android")
|
id("org.jetbrains.kotlin.android")
|
||||||
kotlin("plugin.serialization") version "1.9.21"
|
kotlin("plugin.serialization") version "1.9.22"
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@ -17,7 +17,7 @@ android {
|
|||||||
minSdk = 27
|
minSdk = 27
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = getVersionCode()
|
versionCode = getVersionCode()
|
||||||
versionName = "1.0.9" + ".r${getGitCommitCount()}." + getVersionName()
|
versionName = "1.1.0" + ".r${getGitCommitCount()}." + getVersionName()
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
@ -201,14 +201,8 @@ dependencies {
|
|||||||
implementation("io.coil-kt:coil-compose:2.4.0")
|
implementation("io.coil-kt:coil-compose:2.4.0")
|
||||||
|
|
||||||
implementation(kotlinx("io-jvm", "0.1.16"))
|
implementation(kotlinx("io-jvm", "0.1.16"))
|
||||||
implementation(ktor("server", "core"))
|
|
||||||
implementation(ktor("server", "host-common"))
|
|
||||||
implementation(ktor("server", "status-pages"))
|
|
||||||
implementation(ktor("server", "netty"))
|
|
||||||
implementation(ktor("server", "content-negotiation"))
|
|
||||||
implementation(ktor("client", "core"))
|
implementation(ktor("client", "core"))
|
||||||
implementation(ktor("client", "content-negotiation"))
|
implementation(ktor("client", "okhttp"))
|
||||||
implementation(ktor("client", "cio"))
|
|
||||||
implementation(ktor("serialization", "kotlinx-json"))
|
implementation(ktor("serialization", "kotlinx-json"))
|
||||||
|
|
||||||
implementation(project(":xposed"))
|
implementation(project(":xposed"))
|
||||||
|
21
app/proguard-rules.pro
vendored
21
app/proguard-rules.pro
vendored
@ -199,17 +199,36 @@
|
|||||||
|
|
||||||
-keep class com.arthenica.ffmpegkit.NativeLoader { *; }
|
-keep class com.arthenica.ffmpegkit.NativeLoader { *; }
|
||||||
|
|
||||||
-keep class moe.fuqiuluo.** { *; }
|
-keep class moe.fuqiuluo.shamrock.app.** { *; }
|
||||||
|
-keep class moe.fuqiuluo.shamrock.ui.** { *; }
|
||||||
|
-keep class moe.fuqiuluo.shamrock.MainActivity { *; }
|
||||||
|
-keep class moe.fuqiuluo.shamrock.MainActivityKt { *; }
|
||||||
|
-keep class moe.fuqiuluo.shamrock.Manifest { *; }
|
||||||
|
|
||||||
|
-keep class moe.fuqiuluo.shamrock.xposed.** { *; }
|
||||||
|
-keep class moe.fuqiuluo.shamrock.helper.** { *; }
|
||||||
|
|
||||||
|
# tencent interfaces rules
|
||||||
-keep class com.tencent.** { *; }
|
-keep class com.tencent.** { *; }
|
||||||
-keep class com.qq.** { *; }
|
-keep class com.qq.** { *; }
|
||||||
-keep class com.google.gson.** { *; }
|
-keep class com.google.gson.** { *; }
|
||||||
-keep class de.** { *; }
|
-keep class de.** { *; }
|
||||||
|
-keep class epic.** { *; }
|
||||||
|
-keep class friendlist.** { *; }
|
||||||
|
-keep class KQQ.** { *; }
|
||||||
-keep class mqq.** { *; }
|
-keep class mqq.** { *; }
|
||||||
|
-keep class msf.** { *; }
|
||||||
|
-keep class oicq.** { *; }
|
||||||
-keep class QQService.** { *; }
|
-keep class QQService.** { *; }
|
||||||
-keep class SummaryCard.** { *; }
|
-keep class SummaryCard.** { *; }
|
||||||
-keep class tencent.** { *; }
|
-keep class tencent.** { *; }
|
||||||
|
-keep class VIP.** { *; }
|
||||||
|
|
||||||
-keepclassmembers class * {
|
-keepclassmembers class * {
|
||||||
native <methods>;
|
native <methods>;
|
||||||
}
|
}
|
||||||
-keep class io.netty.** { *; }
|
-keep class io.netty.** { *; }
|
||||||
|
|
||||||
|
-keepclasseswithmembernames class * {
|
||||||
|
native <methods>;
|
||||||
|
}
|
||||||
|
@ -37,7 +37,6 @@ add_library(${CMAKE_PROJECT_NAME} SHARED
|
|||||||
# List C/C++ source files with relative paths to this CMakeLists.txt.
|
# List C/C++ source files with relative paths to this CMakeLists.txt.
|
||||||
${SRC_DIR}
|
${SRC_DIR}
|
||||||
md5.cpp
|
md5.cpp
|
||||||
cqcode.cpp
|
|
||||||
silk.cpp
|
silk.cpp
|
||||||
message.cpp
|
message.cpp
|
||||||
shamrock.cpp)
|
shamrock.cpp)
|
||||||
|
@ -1,138 +0,0 @@
|
|||||||
#include <stdexcept>
|
|
||||||
#include "cqcode.h"
|
|
||||||
|
|
||||||
inline void replace_string(std::string& str, const std::string& from, const std::string& to) {
|
|
||||||
size_t startPos = 0;
|
|
||||||
while ((startPos = str.find(from, startPos)) != std::string::npos) {
|
|
||||||
str.replace(startPos, from.length(), to);
|
|
||||||
startPos += to.length();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline int utf8_next_len(const std::string& str, size_t offset)
|
|
||||||
{
|
|
||||||
uint8_t c = (uint8_t)str[offset];
|
|
||||||
if (c >= 0xFC)
|
|
||||||
return 6;
|
|
||||||
else if (c >= 0xF8)
|
|
||||||
return 5;
|
|
||||||
else if (c >= 0xF0)
|
|
||||||
return 4;
|
|
||||||
else if (c >= 0xE0)
|
|
||||||
return 3;
|
|
||||||
else if (c >= 0xC0)
|
|
||||||
return 2;
|
|
||||||
else if (c > 0x00)
|
|
||||||
return 1;
|
|
||||||
else
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void decode_cqcode(const std::string& code, std::vector<std::unordered_map<std::string, std::string>>& dest) {
|
|
||||||
std::string cache;
|
|
||||||
bool is_start = false;
|
|
||||||
std::string key_tmp;
|
|
||||||
std::unordered_map<std::string, std::string> kv;
|
|
||||||
for(size_t i = 0; i < code.size(); i++) {
|
|
||||||
int utf8_char_len = utf8_next_len(code, i);
|
|
||||||
if(utf8_char_len == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
std::string_view c(&code[i],utf8_char_len);
|
|
||||||
if (c == "[") {
|
|
||||||
if (is_start) {
|
|
||||||
throw illegal_code();
|
|
||||||
} else {
|
|
||||||
if (!cache.empty()) {
|
|
||||||
std::unordered_map<std::string, std::string> kv;
|
|
||||||
replace_string(cache, "[", "[");
|
|
||||||
replace_string(cache, "]", "]");
|
|
||||||
replace_string(cache, "&", "&");
|
|
||||||
kv.emplace("_type", "text");
|
|
||||||
kv.emplace("text", cache);
|
|
||||||
dest.push_back(kv);
|
|
||||||
cache.clear();
|
|
||||||
}
|
|
||||||
std::string_view cq_flag(&code[i],4);
|
|
||||||
if(cq_flag == "[CQ:"){
|
|
||||||
is_start = true;
|
|
||||||
i += 3;
|
|
||||||
}else{
|
|
||||||
cache += c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (c == "=") {
|
|
||||||
if (is_start) {
|
|
||||||
if (cache.empty()) {
|
|
||||||
throw illegal_code();
|
|
||||||
} else {
|
|
||||||
if (key_tmp.empty()) {
|
|
||||||
key_tmp.append(cache);
|
|
||||||
cache.clear();
|
|
||||||
} else {
|
|
||||||
cache += c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cache += c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (c == ",") {
|
|
||||||
if (is_start) {
|
|
||||||
if (kv.count("_type") == 0 && !cache.empty()) {
|
|
||||||
kv.emplace("_type", cache);
|
|
||||||
cache.clear();
|
|
||||||
} else {
|
|
||||||
if (!key_tmp.empty()) {
|
|
||||||
replace_string(cache, "[", "[");
|
|
||||||
replace_string(cache, "]", "]");
|
|
||||||
replace_string(cache, ",", ",");
|
|
||||||
replace_string(cache, "&", "&");
|
|
||||||
kv.emplace(key_tmp, cache);
|
|
||||||
cache.clear();
|
|
||||||
key_tmp.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cache += c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (c == "]") {
|
|
||||||
if (is_start) {
|
|
||||||
if (!cache.empty()) {
|
|
||||||
if (!key_tmp.empty()) {
|
|
||||||
replace_string(cache, "[", "[");
|
|
||||||
replace_string(cache, "]", "]");
|
|
||||||
replace_string(cache, ",", ",");
|
|
||||||
replace_string(cache, "&", "&");
|
|
||||||
kv.emplace(key_tmp, cache);
|
|
||||||
} else {
|
|
||||||
kv.emplace("_type", cache);
|
|
||||||
}
|
|
||||||
dest.push_back(kv);
|
|
||||||
kv.clear();
|
|
||||||
key_tmp.clear();
|
|
||||||
cache.clear();
|
|
||||||
is_start = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cache += c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
cache += c;
|
|
||||||
i += (utf8_char_len - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!cache.empty()) {
|
|
||||||
std::unordered_map<std::string, std::string> kv;
|
|
||||||
replace_string(cache, "[", "[");
|
|
||||||
replace_string(cache, "]", "]");
|
|
||||||
replace_string(cache, "&", "&");
|
|
||||||
kv.emplace("_type", "text");
|
|
||||||
kv.emplace("text", cache);
|
|
||||||
dest.push_back(kv);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,87 +0,0 @@
|
|||||||
#include "jni.h"
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
struct Honor {
|
|
||||||
int id;
|
|
||||||
std::string name;
|
|
||||||
std::string icon_url;
|
|
||||||
int priority;
|
|
||||||
};
|
|
||||||
|
|
||||||
int calc_honor_flag(int honor_id, char honor_flag);
|
|
||||||
|
|
||||||
jobject make_honor_object(JNIEnv *env, jobject user_id, const Honor& honor);
|
|
||||||
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT jobject JNICALL
|
|
||||||
Java_moe_fuqiuluo_shamrock_remote_action_handlers_GetTroopHonor_nativeDecodeHonor(JNIEnv *env, jobject thiz,
|
|
||||||
jstring user_id,
|
|
||||||
jint honor_id,
|
|
||||||
jbyte honor_flag) {
|
|
||||||
static std::vector<Honor> honor_list = {
|
|
||||||
Honor{1, "龙王", "https://qzonestyle.gtimg.cn/aoi/sola/20200213150116_n4PxCiurbm.png", 1},
|
|
||||||
Honor{2, "群聊之火", "https://qzonestyle.gtimg.cn/aoi/sola/20200217190136_92JEGFKC5k.png", 3},
|
|
||||||
Honor{3, "群聊炽焰", "https://qzonestyle.gtimg.cn/aoi/sola/20200217190204_zgCTeSrMq1.png", 4},
|
|
||||||
Honor{5, "冒尖小春笋", "https://qzonestyle.gtimg.cn/aoi/sola/20200213150335_tUJCAtoKVP.png", 5},
|
|
||||||
Honor{6, "快乐源泉", "https://qzonestyle.gtimg.cn/aoi/sola/20200213150434_3tDmsJExCP.png", 7},
|
|
||||||
Honor{7, "学术新星", "https://sola.gtimg.cn/aoi/sola/20200515140645_j0X6gbuHNP.png", 8},
|
|
||||||
Honor{8, "顶尖学霸", "https://sola.gtimg.cn/aoi/sola/20200515140639_0CtWOpfVzK.png", 9},
|
|
||||||
Honor{9, "至尊学神", "https://sola.gtimg.cn/aoi/sola/20200515140628_P8UEYBjMBT.png", 10},
|
|
||||||
Honor{10, "一笔当先", "https://sola.gtimg.cn/aoi/sola/20200515140654_4r94tSCdaB.png", 11},
|
|
||||||
Honor{11, "奋进小翠竹", "https://sola.gtimg.cn/aoi/sola/20200812151819_wbj6z2NGoB.png", 6},
|
|
||||||
Honor{12, "氛围魔杖", "https://sola.gtimg.cn/aoi/sola/20200812151831_4ZJgQCaD1H.png", 2},
|
|
||||||
Honor{13, "壕礼皇冠", "https://sola.gtimg.cn/aoi/sola/20200930154050_juZOAMg7pt.png", 12},
|
|
||||||
};
|
|
||||||
int flag = calc_honor_flag(honor_id, honor_flag);
|
|
||||||
if ((honor_id != 1 && honor_id != 2 && honor_id != 3) || flag != 1) {
|
|
||||||
auto honor = *std::find_if(honor_list.begin(), honor_list.end(), [&honor_id](auto &honor) {
|
|
||||||
return honor.id == honor_id;
|
|
||||||
});
|
|
||||||
return make_honor_object(env, user_id, honor);
|
|
||||||
} else {
|
|
||||||
auto honor = *std::find_if(honor_list.begin(), honor_list.end(), [&honor_id](auto &honor) {
|
|
||||||
return honor.id == honor_id;
|
|
||||||
});
|
|
||||||
std::string url = "https://static-res.qq.com/static-res/groupInteract/vas/a/" + std::to_string(honor_id) + "_1.png";
|
|
||||||
honor = Honor{honor_id, honor.name, url, honor.priority};
|
|
||||||
return make_honor_object(env, user_id, honor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int calc_honor_flag(int honor_id, char honor_flag) {
|
|
||||||
int flag;
|
|
||||||
if (honor_flag == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (honor_id == 1) {
|
|
||||||
flag = honor_flag;
|
|
||||||
} else if (honor_id == 2 || honor_id == 3) {
|
|
||||||
flag = honor_flag >> 2;
|
|
||||||
} else if (honor_id != 4) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
flag = honor_flag >> 4;
|
|
||||||
}
|
|
||||||
return flag & 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
jobject make_honor_object(JNIEnv *env, jobject user_id, const Honor& honor) {
|
|
||||||
jclass GroupMemberHonor = env->FindClass("moe/fuqiuluo/shamrock/remote/service/data/GroupMemberHonor");
|
|
||||||
jmethodID GroupMemberHonor_init = env->GetMethodID(GroupMemberHonor, "<init>",
|
|
||||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILjava/lang/String;)V");
|
|
||||||
auto user_id_str = (jstring) user_id;
|
|
||||||
jstring honor_desc = env->NewStringUTF(honor.name.c_str());
|
|
||||||
jstring uin_name = env->NewStringUTF("");
|
|
||||||
jstring honor_icon_url = env->NewStringUTF(honor.icon_url.c_str());
|
|
||||||
jobject ret = env->NewObject(GroupMemberHonor, GroupMemberHonor_init, user_id_str, uin_name, honor_icon_url, 0, honor.id, honor_desc);
|
|
||||||
|
|
||||||
env->DeleteLocalRef(GroupMemberHonor);
|
|
||||||
env->DeleteLocalRef(user_id_str);
|
|
||||||
env->DeleteLocalRef(honor_desc);
|
|
||||||
env->DeleteLocalRef(honor_icon_url);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
#ifndef UNTITLED_CQCODE_H
|
|
||||||
#define UNTITLED_CQCODE_H
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
#include <exception>
|
|
||||||
|
|
||||||
class illegal_code: std::exception {
|
|
||||||
public:
|
|
||||||
[[nodiscard]] const char * what() const noexcept override {
|
|
||||||
return "Error cq code.";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void decode_cqcode(const std::string& code, std::vector<std::unordered_map<std::string, std::string>>& dest);
|
|
||||||
|
|
||||||
void encode_cqcode(const std::vector<std::unordered_map<std::string, std::string>>& segment, std::string& dest);
|
|
||||||
|
|
||||||
#endif //UNTITLED_CQCODE_H
|
|
@ -1,5 +1,4 @@
|
|||||||
#include "jni.h"
|
#include "jni.h"
|
||||||
#include "cqcode.h"
|
|
||||||
#include <random>
|
#include <random>
|
||||||
|
|
||||||
inline void replace_string(std::string& str, const std::string& from, const std::string& to) {
|
inline void replace_string(std::string& str, const std::string& from, const std::string& to) {
|
||||||
@ -12,7 +11,7 @@ inline void replace_string(std::string& str, const std::string& from, const std:
|
|||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jlong JNICALL
|
JNIEXPORT jlong JNICALL
|
||||||
Java_moe_fuqiuluo_shamrock_helper_MessageHelper_createMessageUniseq(JNIEnv *env, jobject thiz,
|
Java_qq_service_msg_MessageHelper_createMessageUniseq(JNIEnv *env, jobject thiz,
|
||||||
jint chat_type,
|
jint chat_type,
|
||||||
jlong time) {
|
jlong time) {
|
||||||
static std::random_device rd;
|
static std::random_device rd;
|
||||||
@ -32,123 +31,6 @@ Java_moe_fuqiuluo_shamrock_helper_MessageHelper_getChatType(JNIEnv *env, jobject
|
|||||||
return (int32_t) ((int64_t) msg_id & 0xffL);
|
return (int32_t) ((int64_t) msg_id & 0xffL);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT jobject JNICALL
|
|
||||||
Java_moe_fuqiuluo_shamrock_helper_MessageHelper_nativeDecodeCQCode(JNIEnv *env, jobject thiz,
|
|
||||||
jstring code) {
|
|
||||||
jclass ArrayList = env->FindClass("java/util/ArrayList");
|
|
||||||
jmethodID NewArrayList = env->GetMethodID(ArrayList, "<init>", "()V");
|
|
||||||
jmethodID ArrayListAdd = env->GetMethodID(ArrayList, "add", "(Ljava/lang/Object;)Z");
|
|
||||||
jobject arrayList = env->NewObject(ArrayList, NewArrayList);
|
|
||||||
|
|
||||||
const char* cCode = env->GetStringUTFChars(code, nullptr);
|
|
||||||
std::string cppCode = cCode;
|
|
||||||
std::vector<std::unordered_map<std::string, std::string>> dest;
|
|
||||||
try {
|
|
||||||
decode_cqcode(cppCode, dest);
|
|
||||||
} catch (illegal_code& code) {
|
|
||||||
return arrayList;
|
|
||||||
}
|
|
||||||
|
|
||||||
jclass HashMap = env->FindClass("java/util/HashMap");
|
|
||||||
jmethodID NewHashMap = env->GetMethodID(HashMap, "<init>", "()V");
|
|
||||||
jclass String = env->FindClass("java/lang/String");
|
|
||||||
jmethodID NewString = env->GetMethodID(String, "<init>", "([BLjava/lang/String;)V");
|
|
||||||
jstring charset = env->NewStringUTF("UTF-8");
|
|
||||||
jmethodID put = env->GetMethodID(HashMap, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
|
|
||||||
for (auto& map : dest) {
|
|
||||||
jobject hashMap = env->NewObject(HashMap, NewHashMap);
|
|
||||||
for (const auto& pair : map) {
|
|
||||||
jbyteArray keyArray = env->NewByteArray((int) pair.first.size());
|
|
||||||
jbyteArray valueArray = env->NewByteArray((int) pair.second.size());
|
|
||||||
env->SetByteArrayRegion(keyArray, 0, (int) pair.first.size(), (jbyte*)pair.first.c_str());
|
|
||||||
env->SetByteArrayRegion(valueArray, 0, (int) pair.second.size(), (jbyte*)pair.second.c_str());
|
|
||||||
auto key = (jstring) env->NewObject(String, NewString, keyArray, charset);
|
|
||||||
auto value = (jstring) env->NewObject(String, NewString, valueArray, charset);
|
|
||||||
env->CallObjectMethod(hashMap, put, key, value);
|
|
||||||
}
|
|
||||||
env->CallBooleanMethod(arrayList, ArrayListAdd, hashMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
env->DeleteLocalRef(ArrayList);
|
|
||||||
env->DeleteLocalRef(HashMap);
|
|
||||||
env->DeleteLocalRef(String);
|
|
||||||
env->DeleteLocalRef(charset);
|
|
||||||
env->ReleaseStringUTFChars(code, cCode);
|
|
||||||
|
|
||||||
return arrayList;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT jstring JNICALL
|
|
||||||
Java_moe_fuqiuluo_shamrock_helper_MessageHelper_nativeEncodeCQCode(JNIEnv *env, jobject thiz,
|
|
||||||
jobject segment_list) {
|
|
||||||
jclass List = env->FindClass("java/util/List");
|
|
||||||
jmethodID ListSize = env->GetMethodID(List, "size", "()I");
|
|
||||||
jmethodID ListGet = env->GetMethodID(List, "get", "(I)Ljava/lang/Object;");
|
|
||||||
jclass Map = env->FindClass("java/util/Map");
|
|
||||||
jmethodID MapGet = env->GetMethodID(Map, "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
|
|
||||||
jmethodID entrySetMethod = env->GetMethodID(Map, "entrySet", "()Ljava/util/Set;");
|
|
||||||
jclass setClass = env->FindClass("java/util/Set");
|
|
||||||
jmethodID iteratorMethod = env->GetMethodID(setClass, "iterator", "()Ljava/util/Iterator;");
|
|
||||||
jclass entryClass = env->FindClass("java/util/Map$Entry");
|
|
||||||
jmethodID getKeyMethod = env->GetMethodID(entryClass, "getKey", "()Ljava/lang/Object;");
|
|
||||||
jmethodID getValueMethod = env->GetMethodID(entryClass, "getValue", "()Ljava/lang/Object;");
|
|
||||||
|
|
||||||
std::string result;
|
|
||||||
jint size = env->CallIntMethod(segment_list, ListSize);
|
|
||||||
for (int i = 0; i < size; i++ ) {
|
|
||||||
jobject segment = env->CallObjectMethod(segment_list, ListGet, i);
|
|
||||||
jobject entrySet = env->CallObjectMethod(segment, entrySetMethod);
|
|
||||||
jobject iterator = env->CallObjectMethod(entrySet, iteratorMethod);
|
|
||||||
auto type = (jstring) env->CallObjectMethod(segment, MapGet, env->NewStringUTF("_type"));
|
|
||||||
auto typeString = env->GetStringUTFChars(type, nullptr);
|
|
||||||
if (strcmp(typeString, "text") == 0) {
|
|
||||||
auto text = (jstring) env->CallObjectMethod(segment, MapGet, env->NewStringUTF("text"));
|
|
||||||
auto textString = env->GetStringUTFChars(text, nullptr);
|
|
||||||
std::string tmpValue = textString;
|
|
||||||
replace_string(tmpValue, "&", "&");
|
|
||||||
replace_string(tmpValue, "[", "[");
|
|
||||||
replace_string(tmpValue, "]", "]");
|
|
||||||
replace_string(tmpValue, ",", ",");
|
|
||||||
result.append(tmpValue);
|
|
||||||
env->ReleaseStringUTFChars(text, textString);
|
|
||||||
} else {
|
|
||||||
result.append("[CQ:");
|
|
||||||
result.append(typeString);
|
|
||||||
while (env->CallBooleanMethod(iterator, env->GetMethodID(env->GetObjectClass(iterator), "hasNext", "()Z"))) {
|
|
||||||
jobject entry = env->CallObjectMethod(iterator, env->GetMethodID(env->GetObjectClass(iterator), "next", "()Ljava/lang/Object;"));
|
|
||||||
auto key = (jstring) env->CallObjectMethod(entry, getKeyMethod);
|
|
||||||
auto value = (jstring) env->CallObjectMethod(entry, getValueMethod);
|
|
||||||
auto keyString = env->GetStringUTFChars(key, nullptr);
|
|
||||||
auto valueString = env->GetStringUTFChars(value, nullptr);
|
|
||||||
if (strcmp(keyString, "_type") != 0) {
|
|
||||||
std::string tmpValue = valueString;
|
|
||||||
replace_string(tmpValue, "&", "&");
|
|
||||||
replace_string(tmpValue, "[", "[");
|
|
||||||
replace_string(tmpValue, "]", "]");
|
|
||||||
replace_string(tmpValue, ",", ",");
|
|
||||||
result.append(",").append(keyString).append("=").append(tmpValue);
|
|
||||||
}
|
|
||||||
env->ReleaseStringUTFChars(key, keyString);
|
|
||||||
env->ReleaseStringUTFChars(value, valueString);
|
|
||||||
env->DeleteLocalRef(entry);
|
|
||||||
env->DeleteLocalRef(key);
|
|
||||||
env->DeleteLocalRef(value);
|
|
||||||
}
|
|
||||||
result.append("]");
|
|
||||||
}
|
|
||||||
env->ReleaseStringUTFChars(type, typeString);
|
|
||||||
}
|
|
||||||
|
|
||||||
env->DeleteLocalRef(List);
|
|
||||||
env->DeleteLocalRef(Map);
|
|
||||||
env->DeleteLocalRef(setClass);
|
|
||||||
env->DeleteLocalRef(entryClass);
|
|
||||||
return env->NewStringUTF(result.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jlong JNICALL
|
JNIEXPORT jlong JNICALL
|
||||||
Java_moe_fuqiuluo_shamrock_helper_MessageHelper_insertChatTypeToMsgId(JNIEnv *env, jobject thiz,
|
Java_moe_fuqiuluo_shamrock_helper_MessageHelper_insertChatTypeToMsgId(JNIEnv *env, jobject thiz,
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jstring JNICALL
|
JNIEXPORT jstring JNICALL
|
||||||
Java_moe_fuqiuluo_shamrock_xposed_hooks_PullConfig_testNativeLibrary(JNIEnv *env, jobject thiz) {
|
Java_moe_fuqiuluo_shamrock_xposed_actions_interacts_Init_testNativeLibrary(JNIEnv *env, jobject thiz) {
|
||||||
return env->NewStringUTF("加载Shamrock库成功~");
|
return env->NewStringUTF("加载Shamrock库成功~");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ package moe.fuqiuluo.shamrock
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
@ -64,7 +65,9 @@ import androidx.compose.ui.unit.sp
|
|||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import moe.fuqiuluo.shamrock.tools.GlobalUi
|
||||||
import moe.fuqiuluo.shamrock.ui.app.AppRuntime
|
import moe.fuqiuluo.shamrock.ui.app.AppRuntime
|
||||||
import moe.fuqiuluo.shamrock.ui.app.Logger
|
import moe.fuqiuluo.shamrock.ui.app.Logger
|
||||||
import moe.fuqiuluo.shamrock.ui.app.RuntimeState
|
import moe.fuqiuluo.shamrock.ui.app.RuntimeState
|
||||||
@ -85,8 +88,16 @@ import moe.fuqiuluo.shamrock.ui.tools.getShamrockVersion
|
|||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
while (true) {
|
||||||
|
delay(5_000) // Delay in milliseconds
|
||||||
|
broadcastToModule {
|
||||||
|
putExtra("__cmd", "switch_status")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalIndication provides NoIndication
|
LocalIndication provides NoIndication
|
||||||
) {
|
) {
|
||||||
@ -96,8 +107,9 @@ class MainActivity : ComponentActivity() {
|
|||||||
isAppearanceLightStatusBars = true
|
isAppearanceLightStatusBars = true
|
||||||
}
|
}
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, true)
|
WindowCompat.setDecorFitsSystemWindows(window, true)
|
||||||
broadcastToModule { putExtra("__cmd", "fetchPort") }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GlobalUi = Handler(mainLooper)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +165,7 @@ private fun AppMainView() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val ctx = LocalContext.current
|
val ctx = LocalContext.current
|
||||||
LaunchedEffect(isFined.value) {
|
LaunchedEffect(isFined) {
|
||||||
if (isFined.value) {
|
if (isFined.value) {
|
||||||
AppRuntime.log(LocalString.logCentralLoadSuccessfully)
|
AppRuntime.log(LocalString.logCentralLoadSuccessfully)
|
||||||
Toast.makeText(ctx, LocalString.frameworkYes, Toast.LENGTH_SHORT).show()
|
Toast.makeText(ctx, LocalString.frameworkYes, Toast.LENGTH_SHORT).show()
|
||||||
@ -284,58 +296,11 @@ private fun AnimatedTab(
|
|||||||
val lastSelectedState = remember {
|
val lastSelectedState = remember {
|
||||||
mutableIntStateOf(0)
|
mutableIntStateOf(0)
|
||||||
}
|
}
|
||||||
val enter = remember {
|
|
||||||
scaleIn(animationSpec = TweenSpec(150, easing = FastOutLinearInEasing))
|
|
||||||
}
|
|
||||||
val exit = remember {
|
|
||||||
scaleOut(animationSpec = TweenSpec(150, easing = FastOutSlowInEasing))
|
|
||||||
}
|
|
||||||
|
|
||||||
val defaultConst = SELECTED_TABLE[index * 2]
|
val defaultConst = SELECTED_TABLE[index * 2]
|
||||||
val selectedConst = SELECTED_TABLE[(index * 2) + 1]
|
val selectedConst = SELECTED_TABLE[(index * 2) + 1]
|
||||||
val isFirst: Boolean = (lastSelectedState.value and defaultConst) != defaultConst
|
val isFirst: Boolean = (lastSelectedState.value and defaultConst) != defaultConst
|
||||||
|
|
||||||
var icon: @Composable (() -> Unit)? = null
|
|
||||||
var text: @Composable (() -> Unit)? = null
|
|
||||||
|
|
||||||
if (curSelected) {
|
|
||||||
text = {
|
|
||||||
AnimatedVisibility(visibleState = MutableTransitionState(false).also {
|
|
||||||
it.targetState =
|
|
||||||
isFirst || lastSelectedState.value and selectedConst == selectedConst
|
|
||||||
}, enter = enter, exit = exit, modifier = Modifier) {
|
|
||||||
Text(
|
|
||||||
text = titleWithIcon.first,
|
|
||||||
color = GlobalColor.TabItem,
|
|
||||||
fontSize = 15.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(bottom = 5.dp)
|
|
||||||
.indication(
|
|
||||||
remember { MutableInteractionSource() },
|
|
||||||
rememberRipple(color = Color.Transparent)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
icon = {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = titleWithIcon.second),
|
|
||||||
contentDescription = titleWithIcon.first,
|
|
||||||
tint = Color.Unspecified,
|
|
||||||
modifier = Modifier
|
|
||||||
.height(24.dp)
|
|
||||||
.width(24.dp)
|
|
||||||
.padding(bottom = 5.dp)
|
|
||||||
.indication(
|
|
||||||
remember { MutableInteractionSource() },
|
|
||||||
rememberRipple(color = Color.Transparent)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ShamrockTab(
|
ShamrockTab(
|
||||||
selected = curSelected,
|
selected = curSelected,
|
||||||
onClick = {
|
onClick = {
|
||||||
@ -343,11 +308,13 @@ private fun AnimatedTab(
|
|||||||
state.scrollToPage(index, 0f)
|
state.scrollToPage(index, 0f)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
text = text,
|
|
||||||
icon = icon,
|
|
||||||
selectedContentColor = Color.Transparent,
|
selectedContentColor = Color.Transparent,
|
||||||
unselectedContentColor = Color.Transparent,
|
unselectedContentColor = Color.Transparent,
|
||||||
indication = null
|
indication = null,
|
||||||
|
titleWithIcon = titleWithIcon,
|
||||||
|
visibleState = MutableTransitionState(false).also {
|
||||||
|
it.targetState = isFirst || lastSelectedState.value and selectedConst == selectedConst
|
||||||
|
}
|
||||||
)
|
)
|
||||||
lastSelectedState.value.let {
|
lastSelectedState.value.let {
|
||||||
var tmp = it
|
var tmp = it
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
package moe.fuqiuluo.shamrock.app.config
|
||||||
|
|
@ -0,0 +1,33 @@
|
|||||||
|
package moe.fuqiuluo.shamrock.app.config
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import moe.fuqiuluo.shamrock.config.ConfigKey
|
||||||
|
import moe.fuqiuluo.shamrock.ui.service.internal.broadcastToModule
|
||||||
|
|
||||||
|
object ShamrockConfig {
|
||||||
|
internal fun getConfigPref(ctx: Context) = ctx.getSharedPreferences("config", 0)
|
||||||
|
|
||||||
|
internal inline operator fun <reified Type> get(ctx: Context, key: ConfigKey<Type>): Type {
|
||||||
|
val preferences = getConfigPref(ctx)
|
||||||
|
return when(Type::class) {
|
||||||
|
Int::class -> preferences.getInt(key.name(), key.default() as Int) as Type
|
||||||
|
Long::class -> preferences.getLong(key.name(), key.default() as Long) as Type
|
||||||
|
String::class -> preferences.getString(key.name(), key.default() as String) as Type
|
||||||
|
Boolean::class -> preferences.getBoolean(key.name(), key.default() as Boolean) as Type
|
||||||
|
else -> throw IllegalArgumentException("Unsupported type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal inline operator fun <reified Type> set(ctx: Context, key: ConfigKey<Type>, value: Type) {
|
||||||
|
val preferences = getConfigPref(ctx)
|
||||||
|
val editor = preferences.edit()
|
||||||
|
when(Type::class) {
|
||||||
|
Int::class -> editor.putInt(key.name(), value as Int)
|
||||||
|
Long::class -> editor.putLong(key.name(), value as Long)
|
||||||
|
String::class -> editor.putString(key.name(), value as String)
|
||||||
|
Boolean::class -> editor.putBoolean(key.name(), value as Boolean)
|
||||||
|
else -> throw IllegalArgumentException("Unsupported type")
|
||||||
|
}
|
||||||
|
editor.apply()
|
||||||
|
}
|
||||||
|
}
|
@ -1,383 +0,0 @@
|
|||||||
package moe.fuqiuluo.shamrock.ui.app
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import moe.fuqiuluo.shamrock.ui.service.internal.broadcastToModule
|
|
||||||
|
|
||||||
object ShamrockConfig {
|
|
||||||
fun getSSLKeyPath(ctx: Context): String {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getString("key_store", "")!!
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSSLKeyPath(ctx: Context, path: String) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putString("key_store", path).apply()
|
|
||||||
pushUpdate(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSSLPort(ctx: Context): Int {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getInt("ssl_port", 5701)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSSLPort(ctx: Context, port: Int) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putInt("ssl_port", port).apply()
|
|
||||||
pushUpdate(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSSLAlias(ctx: Context): String {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getString("ssl_alias", "")!!
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSSLAlias(ctx: Context, alias: String) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putString("ssl_alias", alias).apply()
|
|
||||||
pushUpdate(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSSLPwd(ctx: Context): String {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getString("ssl_pwd", "")!!
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSSLPwd(ctx: Context, alias: String) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putString("ssl_pwd", alias).apply()
|
|
||||||
pushUpdate(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSSLPrivatePwd(ctx: Context): String {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getString("ssl_private_pwd", "")!!
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSSLPrivatePwd(ctx: Context, alias: String) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putString("ssl_private_pwd", alias).apply()
|
|
||||||
pushUpdate(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getHttpAddr(ctx: Context): String {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getString("http_addr", "")!!
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setHttpAddr(ctx: Context, v: String) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putString("http_addr", v).apply()
|
|
||||||
pushUpdate(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isPro(ctx: Context): Boolean {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getBoolean("pro_api", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setPro(ctx: Context, v: Boolean) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putBoolean("pro_api", v).apply()
|
|
||||||
ctx.broadcastToModule {
|
|
||||||
putExtra("type", "restart")
|
|
||||||
putExtra("__cmd", "change_port")
|
|
||||||
}
|
|
||||||
pushUpdate(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getToken(ctx: Context): String {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getString("token", null) ?: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setToken(ctx: Context, v: String?) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putString("token", v).apply()
|
|
||||||
pushUpdate(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isWs(ctx: Context): Boolean {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getBoolean("ws", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setWs(ctx: Context, v: Boolean) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putBoolean("ws", v).apply()
|
|
||||||
pushUpdate(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isWsClient(ctx: Context): Boolean {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getBoolean("ws_client", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setWsClient(ctx: Context, v: Boolean) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putBoolean("ws_client", v).apply()
|
|
||||||
pushUpdate(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isTablet(ctx: Context): Boolean {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getBoolean("tablet", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setTablet(ctx: Context, v: Boolean) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putBoolean("tablet", v).apply()
|
|
||||||
pushUpdate(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isUseCQCode(ctx: Context): Boolean {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getBoolean("use_cqcode", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setUseCQCode(ctx: Context, v: Boolean) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putBoolean("use_cqcode", v).apply()
|
|
||||||
pushUpdate(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isWebhook(ctx: Context): Boolean {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getBoolean("webhook", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setWebhook(ctx: Context, v: Boolean) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putBoolean("webhook", v).apply()
|
|
||||||
pushUpdate(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getWsAddr(ctx: Context): String {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getString("ws_addr", "")!!
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setWsAddr(ctx: Context, v: String) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putString("ws_addr", v).apply()
|
|
||||||
pushUpdate(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getHttpPort(ctx: Context): Int {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getInt("port", 5700)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setHttpPort(ctx: Context, v: Int) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putInt("port", v).apply()
|
|
||||||
ctx.broadcastToModule {
|
|
||||||
putExtra("type", "port")
|
|
||||||
putExtra("port", v)
|
|
||||||
putExtra("__cmd", "change_port")
|
|
||||||
}
|
|
||||||
pushUpdate(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getWsPort(ctx: Context): Int {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getInt("ws_port", 5800)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setWsPort(ctx: Context, v: Int) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putInt("ws_port", v).apply()
|
|
||||||
ctx.broadcastToModule {
|
|
||||||
putExtra("type", "ws_port")
|
|
||||||
putExtra("port", v)
|
|
||||||
putExtra("__cmd", "change_port")
|
|
||||||
}
|
|
||||||
pushUpdate(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun is2B(ctx: Context): Boolean {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getBoolean("2B", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun set2B(ctx: Context, v: Boolean) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putBoolean("2B", v).apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setAutoClean(ctx: Context, v: Boolean) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putBoolean("auto_clear", v).apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isAutoClean(ctx: Context): Boolean {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getBoolean("auto_clear", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isDebug(ctx: Context): Boolean {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getBoolean("debug", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setDebug(ctx: Context, v: Boolean) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putBoolean("debug", v).apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isAntiTrace(ctx: Context): Boolean {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getBoolean("anti_qq_trace", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isForbidUselessProcess(ctx: Context): Boolean {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getBoolean("forbid_useless_process", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setForbidUselessProcess(ctx: Context, v: Boolean) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putBoolean("forbid_useless_process", v).apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setAntiTrace(ctx: Context, v: Boolean) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putBoolean("anti_qq_trace", v).apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isInjectPacket(ctx: Context): Boolean {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getBoolean("inject_packet", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setInjectPacket(ctx: Context, v: Boolean) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putBoolean("inject_packet", v).apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun enableAutoStart(ctx: Context): Boolean {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getBoolean("enable_auto_start", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun disableAutoSyncSetting(ctx: Context): Boolean {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getBoolean("disable_auto_sync_setting", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun enableAliveReply(ctx: Context): Boolean {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getBoolean("alive_reply", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun allowShell(ctx: Context): Boolean {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getBoolean("shell", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setAutoStart(ctx: Context, v: Boolean) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putBoolean("enable_auto_start", v).apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setDisableAutoSyncSetting(ctx: Context, v: Boolean) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putBoolean("disable_auto_sync_setting", v).apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setAliveReply(ctx: Context, v: Boolean) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putBoolean("alive_reply", v).apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setShellStatus(ctx: Context, v: Boolean) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putBoolean("shell", v).apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun enableSelfMsg(ctx: Context): Boolean {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getBoolean("enable_self_msg", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun enableOldBDH(ctx: Context): Boolean {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getBoolean("enable_old_bdh", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setEnableOldBDH(ctx: Context, v: Boolean) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putBoolean("enable_old_bdh", v).apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun enableSyncMsgAsSentMsg(ctx: Context): Boolean {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return preferences.getBoolean("enable_sync_msg_as_sent_msg", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setEnableSelfMsg(ctx: Context, v: Boolean) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putBoolean("enable_self_msg", v).apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setEnableSyncMsgAsSentMsg(ctx: Context, v: Boolean) {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
preferences.edit().putBoolean("enable_sync_msg_as_sent_msg", v).apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getConfigMap(ctx: Context): Map<String, Any?> {
|
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
|
||||||
return mapOf(
|
|
||||||
"tablet" to preferences.getBoolean("tablet", false),
|
|
||||||
"port" to preferences.getInt("port", 5700),
|
|
||||||
"ws" to preferences.getBoolean("ws", false),
|
|
||||||
"ws_port" to preferences.getInt("ws_port", 5800),
|
|
||||||
"ssl_port" to preferences.getInt("ssl_port", 5701),
|
|
||||||
"http" to preferences.getBoolean("webhook", false),
|
|
||||||
"http_addr" to preferences.getString("http_addr", ""),
|
|
||||||
"ws_client" to preferences.getBoolean("ws_client", false),
|
|
||||||
"use_cqcode" to preferences.getBoolean("use_cqcode", false),
|
|
||||||
"ws_addr" to preferences.getString("ws_addr", ""),
|
|
||||||
"ssl_alias" to preferences.getString("ssl_alias", ""),
|
|
||||||
"pro_api" to preferences.getBoolean("pro_api", false),
|
|
||||||
"token" to preferences.getString("token", null),
|
|
||||||
"ssl_pwd" to preferences.getString("ssl_pwd", ""),
|
|
||||||
"inject_packet" to preferences.getBoolean("inject_packet", false),
|
|
||||||
"debug" to preferences.getBoolean("debug", false),
|
|
||||||
"anti_qq_trace" to preferences.getBoolean("anti_qq_trace", true),
|
|
||||||
"ssl_private_pwd" to preferences.getString("ssl_private_pwd", ""),
|
|
||||||
"key_store" to preferences.getString("key_store", ""),
|
|
||||||
"enable_self_msg" to preferences.getBoolean("enable_self_msg", false),
|
|
||||||
"echo_number" to preferences.getBoolean("echo_number", false),
|
|
||||||
"shell" to preferences.getBoolean("shell", false),
|
|
||||||
"alive_reply" to preferences.getBoolean("alive_reply", false),
|
|
||||||
"enable_sync_msg_as_sent_msg" to preferences.getBoolean("enable_sync_msg_as_sent_msg", false),
|
|
||||||
"disable_auto_sync_setting" to preferences.getBoolean("disable_auto_sync_setting", false),
|
|
||||||
"forbid_useless_process" to preferences.getBoolean("forbid_useless_process", false),
|
|
||||||
"enable_old_bdh" to preferences.getBoolean("enable_old_bdh", false),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun pushUpdate(ctx: Context) {
|
|
||||||
ctx.broadcastToModule {
|
|
||||||
getConfigMap(ctx).forEach { (key, value) ->
|
|
||||||
if (value == null) {
|
|
||||||
val v: String? = null
|
|
||||||
this.putExtra(key, v)
|
|
||||||
} else {
|
|
||||||
when (value) {
|
|
||||||
is Int -> this.putExtra(key, value)
|
|
||||||
is Long -> this.putExtra(key, value)
|
|
||||||
is Short -> this.putExtra(key, value)
|
|
||||||
is Byte -> this.putExtra(key, value)
|
|
||||||
is String -> this.putExtra(key, value)
|
|
||||||
is ByteArray -> this.putExtra(key, value)
|
|
||||||
is Boolean -> this.putExtra(key, value)
|
|
||||||
is Float -> this.putExtra(key, value)
|
|
||||||
is Double -> this.putExtra(key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
putExtra("__cmd", "push_config")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,7 +5,6 @@ import android.content.ClipData
|
|||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
@ -25,6 +24,7 @@ import androidx.compose.material3.Divider
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@ -45,10 +45,12 @@ import coil.compose.rememberAsyncImagePainter
|
|||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import coil.size.Size
|
import coil.size.Size
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import moe.fuqiuluo.shamrock.R
|
import moe.fuqiuluo.shamrock.R
|
||||||
import moe.fuqiuluo.shamrock.ui.app.AppRuntime
|
import moe.fuqiuluo.shamrock.ui.app.AppRuntime
|
||||||
import moe.fuqiuluo.shamrock.ui.app.Level
|
import moe.fuqiuluo.shamrock.ui.app.Level
|
||||||
import moe.fuqiuluo.shamrock.ui.app.ShamrockConfig
|
import moe.fuqiuluo.shamrock.app.config.ShamrockConfig
|
||||||
|
import moe.fuqiuluo.shamrock.config.*
|
||||||
import moe.fuqiuluo.shamrock.ui.theme.GlobalColor
|
import moe.fuqiuluo.shamrock.ui.theme.GlobalColor
|
||||||
import moe.fuqiuluo.shamrock.ui.theme.LocalString
|
import moe.fuqiuluo.shamrock.ui.theme.LocalString
|
||||||
import moe.fuqiuluo.shamrock.ui.theme.ThemeColor
|
import moe.fuqiuluo.shamrock.ui.theme.ThemeColor
|
||||||
@ -72,110 +74,6 @@ fun DashboardFragment(
|
|||||||
InformationCard(ctx)
|
InformationCard(ctx)
|
||||||
APIInfoCard(ctx)
|
APIInfoCard(ctx)
|
||||||
FunctionCard(scope, ctx, LocalString.functionSetting)
|
FunctionCard(scope, ctx, LocalString.functionSetting)
|
||||||
SSLCard(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun SSLCard(ctx: Context) {
|
|
||||||
ActionBox(
|
|
||||||
modifier = Modifier.padding(top = 12.dp),
|
|
||||||
painter = painterResource(id = R.drawable.baseline_security_24),
|
|
||||||
title = LocalString.sslSetting
|
|
||||||
) {
|
|
||||||
Column {
|
|
||||||
Divider(
|
|
||||||
modifier = Modifier,
|
|
||||||
color = GlobalColor.Divider,
|
|
||||||
thickness = 0.2.dp
|
|
||||||
)
|
|
||||||
|
|
||||||
val sslPort = remember { mutableStateOf(ShamrockConfig.getSSLPort(ctx).toString()) }
|
|
||||||
TextItem(
|
|
||||||
title = "SSL端口",
|
|
||||||
desc = "端口范围在0~65565,并确保可用。",
|
|
||||||
text = sslPort,
|
|
||||||
hint = "请输入端口号",
|
|
||||||
error = "端口范围应在0~65565",
|
|
||||||
checker = {
|
|
||||||
it.isNotBlank() && kotlin.runCatching { it.toInt() in 0..65565 }.getOrDefault(false)
|
|
||||||
},
|
|
||||||
confirm = {
|
|
||||||
val newPort = sslPort.value.toInt()
|
|
||||||
ShamrockConfig.setSSLPort(ctx, newPort)
|
|
||||||
AppRuntime.log("设置SSL(HTTP)端口为$newPort,立即生效尝试中。")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val keyStore = remember { mutableStateOf(ShamrockConfig.getSSLKeyPath(ctx)) }
|
|
||||||
TextItem(
|
|
||||||
title = "SSL证书",
|
|
||||||
desc = "BKS签名的证书。",
|
|
||||||
text = keyStore,
|
|
||||||
hint = "输入证书路径",
|
|
||||||
error = "证书路径不合法或不存在",
|
|
||||||
checker = {
|
|
||||||
it.isNotBlank()
|
|
||||||
},
|
|
||||||
confirm = {
|
|
||||||
val new = keyStore.value
|
|
||||||
ShamrockConfig.setSSLKeyPath(ctx, new)
|
|
||||||
AppRuntime.log("设置SSL证书为[$new]。")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val alias = remember { mutableStateOf(ShamrockConfig.getSSLAlias(ctx)) }
|
|
||||||
TextItem(
|
|
||||||
title = "SSL别名",
|
|
||||||
desc = "BKS签名的别名,确保大小写区分正确。",
|
|
||||||
text = alias,
|
|
||||||
hint = "输入签名别名",
|
|
||||||
error = "别名不合法",
|
|
||||||
checker = {
|
|
||||||
it.isNotBlank()
|
|
||||||
},
|
|
||||||
confirm = {
|
|
||||||
val new = alias.value
|
|
||||||
ShamrockConfig.setSSLAlias(ctx, new)
|
|
||||||
AppRuntime.log("设置SSL别名为[$new]。")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val sslPwd = remember { mutableStateOf(ShamrockConfig.getSSLPwd(ctx)) }
|
|
||||||
TextItem(
|
|
||||||
title = "SSL密码",
|
|
||||||
desc = "BKS签名的密码。",
|
|
||||||
text = sslPwd,
|
|
||||||
hint = "输入签名密码",
|
|
||||||
error = "密码不合法",
|
|
||||||
checker = {
|
|
||||||
it.isNotBlank()
|
|
||||||
},
|
|
||||||
confirm = {
|
|
||||||
val new = sslPwd.value
|
|
||||||
ShamrockConfig.setSSLPwd(ctx, new)
|
|
||||||
AppRuntime.log("设置SSL密码为[$new]。")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val sslPrivatePwd = remember { mutableStateOf(ShamrockConfig.getSSLPrivatePwd(ctx)) }
|
|
||||||
TextItem(
|
|
||||||
title = "SSL Private密码",
|
|
||||||
desc = "BKS签名的Private密码。",
|
|
||||||
text = sslPrivatePwd,
|
|
||||||
hint = "输入Private密码",
|
|
||||||
error = "密码不合法",
|
|
||||||
checker = {
|
|
||||||
it.isNotBlank()
|
|
||||||
},
|
|
||||||
confirm = {
|
|
||||||
val new = sslPrivatePwd.value
|
|
||||||
ShamrockConfig.setSSLPrivatePwd(ctx, new)
|
|
||||||
AppRuntime.log("设置SSL Private密码为[$new]。")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,93 +93,35 @@ private fun APIInfoCard(
|
|||||||
thickness = 0.2.dp
|
thickness = 0.2.dp
|
||||||
)
|
)
|
||||||
|
|
||||||
val wsPort = remember { mutableStateOf(ShamrockConfig.getWsPort(ctx).toString()) }
|
val rpcPort = remember { mutableStateOf(ShamrockConfig[ctx, RPCPort].toString()) }
|
||||||
val port = remember { mutableStateOf(ShamrockConfig.getHttpPort(ctx).toString()) }
|
|
||||||
TextItem(
|
TextItem(
|
||||||
title = "主动HTTP端口",
|
title = "RPC服务端口",
|
||||||
desc = "端口范围在0~65565,并确保可用。",
|
desc = "端口范围在0~65565,并确保可用。",
|
||||||
text = port,
|
text = rpcPort,
|
||||||
hint = "请输入端口号",
|
hint = "请输入端口号",
|
||||||
error = "端口范围应在0~65565",
|
error = "端口范围应在0~65565",
|
||||||
checker = {
|
checker = {
|
||||||
it.isNotBlank() && kotlin.runCatching { it.toInt() in 0..65565 }.getOrDefault(false) && wsPort.value != it
|
it.isNotBlank() && kotlin.runCatching { it.toInt() in 0..65565 }
|
||||||
|
.getOrDefault(false) && rpcPort.value != it
|
||||||
},
|
},
|
||||||
confirm = {
|
confirm = {
|
||||||
val newPort = port.value.toInt()
|
val newPort = rpcPort.value.toInt()
|
||||||
ShamrockConfig.setHttpPort(ctx, newPort)
|
ShamrockConfig[ctx, RPCPort] = newPort
|
||||||
AppRuntime.log("设置主动HTTP监听端口为$newPort,立即生效尝试中。")
|
AppRuntime.log("设置主动HTTP监听端口为$newPort,立即生效尝试中。")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val rpcAddress = remember { mutableStateOf(ShamrockConfig[ctx, RPCAddress]) }
|
||||||
TextItem(
|
TextItem(
|
||||||
title = "主动WebSocket端口",
|
title = "回调RPC地址",
|
||||||
desc = "端口范围在0~65565,并确保可用。",
|
desc = "例如:kritor.support:8081",
|
||||||
text = wsPort,
|
text = rpcAddress,
|
||||||
hint = "请输入端口号",
|
|
||||||
error = "端口范围应在0~65565",
|
|
||||||
checker = {
|
|
||||||
it.isNotBlank() && kotlin.runCatching { it.toInt() in 0..65565 }.getOrDefault(false) && it != port.value
|
|
||||||
},
|
|
||||||
confirm = {
|
|
||||||
val newPort = wsPort.value.toInt()
|
|
||||||
ShamrockConfig.setWsPort(ctx, newPort)
|
|
||||||
AppRuntime.log("设置主动WebSocket监听端口为$newPort。")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val webHookAddress = remember { mutableStateOf(ShamrockConfig.getHttpAddr(ctx)) }
|
|
||||||
TextItem(
|
|
||||||
title = "回调HTTP地址",
|
|
||||||
desc = "例如:http://shamrock.moe:80。",
|
|
||||||
text = webHookAddress,
|
|
||||||
hint = "请输入回调地址",
|
hint = "请输入回调地址",
|
||||||
error = "输入的地址不合法",
|
error = "输入的地址不合法",
|
||||||
checker = {
|
|
||||||
it.isNotBlank()
|
|
||||||
},
|
|
||||||
confirm = {
|
|
||||||
if (it.startsWith("http://") || it.startsWith("https://")) {
|
|
||||||
ShamrockConfig.setHttpAddr(ctx, webHookAddress.value)
|
|
||||||
AppRuntime.log("设置回调HTTP地址为[${webHookAddress.value}]。")
|
|
||||||
} else {
|
|
||||||
Toast.makeText(ctx, "回调地址不合法", Toast.LENGTH_SHORT).show()
|
|
||||||
webHookAddress.value = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val wsAddress = remember { mutableStateOf(ShamrockConfig.getWsAddr(ctx)) }
|
|
||||||
TextItem(
|
|
||||||
title = "被动WebSocket地址",
|
|
||||||
desc = "例如:ws://shamrock.moe:81,多个使用逗号分隔。",
|
|
||||||
text = wsAddress,
|
|
||||||
hint = "请输入被动地址",
|
|
||||||
error = "输入的地址不合法",
|
|
||||||
checker = {
|
|
||||||
true
|
|
||||||
},
|
|
||||||
confirm = {
|
|
||||||
if (it.startsWith("ws://") || it.startsWith("wss://") || it.isBlank()) {
|
|
||||||
ShamrockConfig.setWsAddr(ctx, wsAddress.value)
|
|
||||||
AppRuntime.log("设置被动WebSocket地址为[${wsAddress.value}]。")
|
|
||||||
} else {
|
|
||||||
Toast.makeText(ctx, "被动WebSocket地址不合法", Toast.LENGTH_SHORT).show()
|
|
||||||
wsAddress.value = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val authToken = remember { mutableStateOf(ShamrockConfig.getToken(ctx)) }
|
|
||||||
TextItem(
|
|
||||||
title = "鉴权Token",
|
|
||||||
desc = "用于鉴权的Token。",
|
|
||||||
text = authToken,
|
|
||||||
hint = "请填写鉴权token",
|
|
||||||
error = "输入的参数不合法",
|
|
||||||
checker = { true },
|
checker = { true },
|
||||||
confirm = {
|
confirm = {
|
||||||
ShamrockConfig.setToken(ctx, authToken.value)
|
ShamrockConfig[ctx, RPCAddress] = rpcAddress.value
|
||||||
AppRuntime.log("设置鉴权Token为[${authToken.value}]。")
|
AppRuntime.log("设置回调RPC地址为[${rpcAddress.value}]。")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -314,59 +154,59 @@ private fun FunctionCard(
|
|||||||
Function(
|
Function(
|
||||||
title = "强制平板模式",
|
title = "强制平板模式",
|
||||||
desc = "强制QQ使用平板模式,实现共存登录。",
|
desc = "强制QQ使用平板模式,实现共存登录。",
|
||||||
isSwitch = ShamrockConfig.isTablet(ctx)
|
isSwitch = ShamrockConfig[ctx, ForceTablet]
|
||||||
) {
|
) {
|
||||||
ShamrockConfig.setTablet(ctx, it)
|
ShamrockConfig[ctx, ForceTablet] = it
|
||||||
return@Function true
|
return@Function true
|
||||||
}
|
}
|
||||||
|
|
||||||
Function(
|
Function(
|
||||||
title = "HTTP回调",
|
title = "主动RPC",
|
||||||
desc = "OneBot标准的HTTPAPI回调,Shamrock作为Client。",
|
desc = "Kritor协议实现RPC",
|
||||||
isSwitch = ShamrockConfig.isWebhook(ctx)
|
isSwitch = ShamrockConfig[ctx, ActiveRPC]
|
||||||
) {
|
) {
|
||||||
ShamrockConfig.setWebhook(ctx, it)
|
ShamrockConfig[ctx, ActiveRPC] = it
|
||||||
return@Function true
|
return@Function true
|
||||||
}
|
}
|
||||||
|
|
||||||
Function(
|
Function(
|
||||||
title = "消息格式为CQ码",
|
title = "被动RPC",
|
||||||
desc = "HTTPAPI回调的消息格式,关闭则为消息段。",
|
desc = "Kritor协议实现RPC",
|
||||||
isSwitch = ShamrockConfig.isUseCQCode(ctx)
|
isSwitch = ShamrockConfig[ctx, PassiveRPC]
|
||||||
) {
|
) {
|
||||||
ShamrockConfig.setUseCQCode(ctx, it)
|
ShamrockConfig[ctx, PassiveRPC] = it
|
||||||
return@Function true
|
return@Function true
|
||||||
}
|
}
|
||||||
|
|
||||||
Function(
|
run {
|
||||||
title = "主动WebSocket",
|
val uploadResourceGroup = remember { mutableStateOf(ShamrockConfig[ctx, ResourceGroup]) }
|
||||||
desc = "OneBot标准WebSocket,Shamrock作为Server。",
|
Column(
|
||||||
isSwitch = ShamrockConfig.isWs(ctx)
|
modifier = Modifier
|
||||||
) {
|
.absolutePadding(left = 8.dp, right = 8.dp, top = 12.dp, bottom = 0.dp)
|
||||||
ShamrockConfig.setWs(ctx, it)
|
) {
|
||||||
return@Function true
|
Text(
|
||||||
|
modifier = Modifier.padding(2.dp),
|
||||||
|
text = "用来上传资源的群聊,错误的资源上传终点可能导致封禁,请自建一个群聊并填写在下方。",
|
||||||
|
color = Color.Red,
|
||||||
|
fontSize = 11.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
TextItem(
|
||||||
|
title = "接受资源群聊",
|
||||||
|
desc = "用来上传资源的群聊,请自建一个群聊并填写在下方。",
|
||||||
|
text = uploadResourceGroup,
|
||||||
|
hint = "请输入群号",
|
||||||
|
error = "群号不合法",
|
||||||
|
checker = {
|
||||||
|
it.isNotBlank() && it.toULongOrNull() != null
|
||||||
|
},
|
||||||
|
confirm = {
|
||||||
|
val groupId = uploadResourceGroup.value
|
||||||
|
ShamrockConfig[ctx, ResourceGroup] = groupId
|
||||||
|
AppRuntime.log("设置接受资源群聊为[$groupId]。")
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Function(
|
|
||||||
title = "被动WebSocket",
|
|
||||||
desc = "OneBot标准WebSocket,Shamrock作为Client。",
|
|
||||||
isSwitch = ShamrockConfig.isWsClient(ctx)
|
|
||||||
) {
|
|
||||||
ShamrockConfig.setWsClient(ctx, it)
|
|
||||||
return@Function true
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Function(
|
|
||||||
title = "专业级接口",
|
|
||||||
desc = "如果你不知道你在做什么,请不要开启本功能。",
|
|
||||||
descColor = Color.Red,
|
|
||||||
isSwitch = ShamrockConfig.isPro(ctx)
|
|
||||||
) {
|
|
||||||
ShamrockConfig.setPro(ctx, it)
|
|
||||||
AppRuntime.log("专业级API = $it", Level.WARN)
|
|
||||||
return@Function true
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -445,9 +285,7 @@ private fun InfoItem(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.combinedClickable(onDoubleClick = {
|
.combinedClickable(onDoubleClick = {
|
||||||
doubleClick?.invoke(content)
|
doubleClick?.invoke(content)
|
||||||
}) {
|
}) { true }
|
||||||
true
|
|
||||||
}
|
|
||||||
,
|
,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
|
@ -23,7 +23,14 @@ import androidx.compose.ui.unit.sp
|
|||||||
import moe.fuqiuluo.shamrock.R
|
import moe.fuqiuluo.shamrock.R
|
||||||
import moe.fuqiuluo.shamrock.ui.app.AppRuntime
|
import moe.fuqiuluo.shamrock.ui.app.AppRuntime
|
||||||
import moe.fuqiuluo.shamrock.ui.app.Level
|
import moe.fuqiuluo.shamrock.ui.app.Level
|
||||||
import moe.fuqiuluo.shamrock.ui.app.ShamrockConfig
|
import moe.fuqiuluo.shamrock.app.config.ShamrockConfig
|
||||||
|
import moe.fuqiuluo.shamrock.config.AliveReply
|
||||||
|
import moe.fuqiuluo.shamrock.config.AntiJvmTrace
|
||||||
|
import moe.fuqiuluo.shamrock.config.B2Mode
|
||||||
|
import moe.fuqiuluo.shamrock.config.DebugMode
|
||||||
|
import moe.fuqiuluo.shamrock.config.EnableOldBDH
|
||||||
|
import moe.fuqiuluo.shamrock.config.EnableSelfMessage
|
||||||
|
import moe.fuqiuluo.shamrock.ui.service.handlers.InitHandler
|
||||||
import moe.fuqiuluo.shamrock.ui.theme.GlobalColor
|
import moe.fuqiuluo.shamrock.ui.theme.GlobalColor
|
||||||
import moe.fuqiuluo.shamrock.ui.theme.LocalString
|
import moe.fuqiuluo.shamrock.ui.theme.LocalString
|
||||||
import moe.fuqiuluo.shamrock.ui.tools.NoticeTextDialog
|
import moe.fuqiuluo.shamrock.ui.tools.NoticeTextDialog
|
||||||
@ -68,9 +75,9 @@ fun LabFragment() {
|
|||||||
title = LocalString.b2Mode,
|
title = LocalString.b2Mode,
|
||||||
desc = LocalString.b2ModeDesc,
|
desc = LocalString.b2ModeDesc,
|
||||||
descColor = it,
|
descColor = it,
|
||||||
isSwitch = ShamrockConfig.is2B(ctx)
|
isSwitch = ShamrockConfig[ctx, B2Mode]
|
||||||
) {
|
) {
|
||||||
ShamrockConfig.set2B(ctx, it)
|
ShamrockConfig[ctx, B2Mode] = it
|
||||||
scope.toast(ctx, LocalString.restartToast)
|
scope.toast(ctx, LocalString.restartToast)
|
||||||
return@Function true
|
return@Function true
|
||||||
}
|
}
|
||||||
@ -79,10 +86,10 @@ fun LabFragment() {
|
|||||||
title = LocalString.showDebugLog,
|
title = LocalString.showDebugLog,
|
||||||
desc = LocalString.showDebugLogDesc,
|
desc = LocalString.showDebugLogDesc,
|
||||||
descColor = it,
|
descColor = it,
|
||||||
isSwitch = ShamrockConfig.isDebug(ctx)
|
isSwitch = ShamrockConfig[ctx, DebugMode]
|
||||||
) {
|
) {
|
||||||
ShamrockConfig.setDebug(ctx, it)
|
ShamrockConfig[ctx, DebugMode] = it
|
||||||
ShamrockConfig.pushUpdate(ctx)
|
InitHandler.update(ctx)
|
||||||
return@Function true
|
return@Function true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,54 +107,13 @@ fun LabFragment() {
|
|||||||
thickness = 0.2.dp
|
thickness = 0.2.dp
|
||||||
)
|
)
|
||||||
|
|
||||||
Function(
|
|
||||||
title = "禁止无用进程",
|
|
||||||
desc = "禁止QQ生成无用进程浪费内存,可能造成部分功能闪退。",
|
|
||||||
descColor = color,
|
|
||||||
isSwitch = ShamrockConfig.isForbidUselessProcess(ctx)
|
|
||||||
) {
|
|
||||||
ShamrockConfig.setForbidUselessProcess(ctx, it)
|
|
||||||
ShamrockConfig.pushUpdate(ctx)
|
|
||||||
return@Function true
|
|
||||||
}
|
|
||||||
|
|
||||||
Function(
|
Function(
|
||||||
title = "自回复测试",
|
title = "自回复测试",
|
||||||
desc = "发送[ping],机器人发送一个具有调试信息的返回。",
|
desc = "发送[ping],机器人发送一个具有调试信息的返回。",
|
||||||
descColor = color,
|
descColor = color,
|
||||||
isSwitch = ShamrockConfig.enableAliveReply(ctx)
|
isSwitch = ShamrockConfig[ctx, AliveReply]
|
||||||
) {
|
) {
|
||||||
ShamrockConfig.setAliveReply(ctx, it)
|
ShamrockConfig[ctx, AliveReply] = it
|
||||||
return@Function true
|
|
||||||
}
|
|
||||||
|
|
||||||
Function(
|
|
||||||
title = "开启Shell接口",
|
|
||||||
desc = "可能导致设备被入侵,请勿随意开启。",
|
|
||||||
descColor = color,
|
|
||||||
isSwitch = ShamrockConfig.allowShell(ctx)
|
|
||||||
) {
|
|
||||||
ShamrockConfig.setShellStatus(ctx, it)
|
|
||||||
return@Function true
|
|
||||||
}
|
|
||||||
|
|
||||||
Function(
|
|
||||||
title = "自动唤醒QQ",
|
|
||||||
desc = "QQ进程死亡时重新打开QQ进程,前提本进程存活。",
|
|
||||||
descColor = color,
|
|
||||||
isSwitch = ShamrockConfig.enableAutoStart(ctx)
|
|
||||||
) {
|
|
||||||
ShamrockConfig.setAutoStart(ctx, it)
|
|
||||||
return@Function true
|
|
||||||
}
|
|
||||||
|
|
||||||
Function(
|
|
||||||
title = "禁止Shamrock同步设置",
|
|
||||||
desc = "禁止Shamrock同步设置,防止恢复手动修改后的配置文件。",
|
|
||||||
descColor = color,
|
|
||||||
isSwitch = ShamrockConfig.disableAutoSyncSetting(ctx)
|
|
||||||
) {
|
|
||||||
ShamrockConfig.setDisableAutoSyncSetting(ctx, it)
|
|
||||||
return@Function true
|
return@Function true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,25 +160,14 @@ fun LabFragment() {
|
|||||||
thickness = 0.2.dp
|
thickness = 0.2.dp
|
||||||
)
|
)
|
||||||
|
|
||||||
Function(
|
|
||||||
title = LocalString.injectPacket,
|
|
||||||
desc = LocalString.injectPacketDesc,
|
|
||||||
descColor = color,
|
|
||||||
isSwitch = ShamrockConfig.isInjectPacket(ctx)
|
|
||||||
) {
|
|
||||||
ShamrockConfig.setInjectPacket(ctx, it)
|
|
||||||
ShamrockConfig.pushUpdate(ctx)
|
|
||||||
return@Function true
|
|
||||||
}
|
|
||||||
|
|
||||||
Function(
|
Function(
|
||||||
title = LocalString.antiTrace,
|
title = LocalString.antiTrace,
|
||||||
desc = LocalString.antiTraceDesc,
|
desc = LocalString.antiTraceDesc,
|
||||||
descColor = color,
|
descColor = color,
|
||||||
isSwitch = ShamrockConfig.isAntiTrace(ctx)
|
isSwitch = ShamrockConfig[ctx, AntiJvmTrace]
|
||||||
) {
|
) {
|
||||||
ShamrockConfig.setAntiTrace(ctx, it)
|
ShamrockConfig[ctx, AntiJvmTrace] = it
|
||||||
ShamrockConfig.pushUpdate(ctx)
|
scope.toast(ctx, LocalString.restartToast)
|
||||||
return@Function true
|
return@Function true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,21 +232,10 @@ fun LabFragment() {
|
|||||||
title = "自发消息推送",
|
title = "自发消息推送",
|
||||||
desc = "推送Bot发送的消息,未做特殊处理请勿打开。",
|
desc = "推送Bot发送的消息,未做特殊处理请勿打开。",
|
||||||
descColor = it,
|
descColor = it,
|
||||||
isSwitch = ShamrockConfig.enableSelfMsg(ctx)
|
isSwitch = ShamrockConfig[ctx, EnableSelfMessage]
|
||||||
) {
|
) {
|
||||||
ShamrockConfig.setEnableSelfMsg(ctx, it)
|
ShamrockConfig[ctx, EnableSelfMessage] = it
|
||||||
ShamrockConfig.pushUpdate(ctx)
|
InitHandler.update(ctx)
|
||||||
return@Function true
|
|
||||||
}
|
|
||||||
|
|
||||||
Function(
|
|
||||||
title = "同步消息推送类型异换",
|
|
||||||
desc = "推送来自同号异设备消息,将同步消息作为自发消息推送。",
|
|
||||||
descColor = it,
|
|
||||||
isSwitch = ShamrockConfig.enableSyncMsgAsSentMsg(ctx)
|
|
||||||
) {
|
|
||||||
ShamrockConfig.setEnableSyncMsgAsSentMsg(ctx, it)
|
|
||||||
ShamrockConfig.pushUpdate(ctx)
|
|
||||||
return@Function true
|
return@Function true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,10 +243,10 @@ fun LabFragment() {
|
|||||||
title = "启用旧版资源上传系统",
|
title = "启用旧版资源上传系统",
|
||||||
desc = "如果NT内核无法上传资源,请打开本开关。",
|
desc = "如果NT内核无法上传资源,请打开本开关。",
|
||||||
descColor = it,
|
descColor = it,
|
||||||
isSwitch = ShamrockConfig.enableOldBDH(ctx)
|
isSwitch = ShamrockConfig[ctx, EnableOldBDH]
|
||||||
) {
|
) {
|
||||||
ShamrockConfig.setEnableOldBDH(ctx, it)
|
ShamrockConfig[ctx, EnableOldBDH] = it
|
||||||
ShamrockConfig.pushUpdate(ctx)
|
InitHandler.update(ctx)
|
||||||
return@Function true
|
return@Function true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,108 +0,0 @@
|
|||||||
@file:OptIn(DelicateCoroutinesApi::class)
|
|
||||||
package moe.fuqiuluo.shamrock.ui.service
|
|
||||||
|
|
||||||
import android.content.ComponentName
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.core.content.ContextCompat.startActivity
|
|
||||||
import io.ktor.client.request.get
|
|
||||||
import io.ktor.client.request.parameter
|
|
||||||
import io.ktor.client.request.url
|
|
||||||
import io.ktor.client.statement.bodyAsText
|
|
||||||
import io.ktor.http.HttpStatusCode
|
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import moe.fuqiuluo.shamrock.remote.structures.CommonResult
|
|
||||||
import moe.fuqiuluo.shamrock.remote.structures.CurrentAccount
|
|
||||||
import moe.fuqiuluo.shamrock.remote.structures.Status
|
|
||||||
import moe.fuqiuluo.shamrock.tools.GlobalClient
|
|
||||||
import moe.fuqiuluo.shamrock.ui.app.AppRuntime.AccountInfo
|
|
||||||
import moe.fuqiuluo.shamrock.ui.app.AppRuntime.log
|
|
||||||
import moe.fuqiuluo.shamrock.ui.app.AppRuntime.state
|
|
||||||
import moe.fuqiuluo.shamrock.ui.app.Level
|
|
||||||
import moe.fuqiuluo.shamrock.ui.app.ShamrockConfig
|
|
||||||
import moe.fuqiuluo.shamrock.ui.service.internal.broadcastToModule
|
|
||||||
import java.net.ConnectException
|
|
||||||
import java.util.Timer
|
|
||||||
import kotlin.concurrent.timer
|
|
||||||
|
|
||||||
object DashboardInitializer {
|
|
||||||
private var servicePort: Int = 0
|
|
||||||
private lateinit var heartbeatTimer: Timer
|
|
||||||
|
|
||||||
operator fun invoke(context: Context, port: Int) {
|
|
||||||
servicePort = port
|
|
||||||
initHeartbeat(true, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initHeartbeat(reload: Boolean, context: Context) {
|
|
||||||
if (::heartbeatTimer.isInitialized && !reload) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (::heartbeatTimer.isInitialized) {
|
|
||||||
heartbeatTimer.cancel()
|
|
||||||
}
|
|
||||||
heartbeatTimer = timer("heartbeat", false, 0, 1000L * 30) {
|
|
||||||
checkService(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkService(context: Context) {
|
|
||||||
GlobalScope.launch {
|
|
||||||
try {
|
|
||||||
GlobalClient.get {
|
|
||||||
url("http://127.0.0.1:$servicePort/get_account_info")
|
|
||||||
val token = ShamrockConfig.getToken(context)
|
|
||||||
if (token.isNotBlank()) {
|
|
||||||
//header("Authorization", "Bearer $token")
|
|
||||||
parameter("access_token", token)
|
|
||||||
}
|
|
||||||
}.let {
|
|
||||||
if (it.status == HttpStatusCode.OK) {
|
|
||||||
val result: CommonResult<CurrentAccount> = Json.decodeFromString(it.bodyAsText())
|
|
||||||
state.isFined.value = result.retcode == 0
|
|
||||||
if (result.retcode == Status.InternalHandlerError.code) {
|
|
||||||
log("账号未登录。", Level.WARN)
|
|
||||||
} else if (result.retcode != 0) {
|
|
||||||
log("尝试从接口获取账号信息失败,未知错误。", Level.ERROR)
|
|
||||||
} else {
|
|
||||||
AccountInfo.let { account ->
|
|
||||||
account.uin.value = result.data.uin.toString()
|
|
||||||
account.nick.value = result.data.nick
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
state.isFined.value = false
|
|
||||||
log("尝试从接口获取账号信息失败,服务运行异常。", Level.ERROR)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: ConnectException) {
|
|
||||||
state.isFined.value = false
|
|
||||||
context.broadcastToModule {
|
|
||||||
putExtra("__cmd", "checkAndStartService")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ShamrockConfig.enableAutoStart(context)) {
|
|
||||||
log("检测到Service死亡,正在尝试重新启动!")
|
|
||||||
GlobalScope.launch(Dispatchers.Main) {
|
|
||||||
val packageName = "com.tencent.mobileqq"
|
|
||||||
val className = "com.tencent.mobileqq.activity.SplashActivity"
|
|
||||||
|
|
||||||
val intent = Intent()
|
|
||||||
intent.component = ComponentName(packageName, className)
|
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
||||||
intent.putExtra("from", "shamrock")
|
|
||||||
startActivity(context, intent, Bundle.EMPTY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
state.isFined.value = false
|
|
||||||
log(e.stackTraceToString(), Level.ERROR)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
package moe.fuqiuluo.shamrock.ui.service.handlers
|
|
||||||
|
|
||||||
import android.content.ContentValues
|
|
||||||
import android.content.Context
|
|
||||||
import moe.fuqiuluo.shamrock.ui.app.AppRuntime
|
|
||||||
import moe.fuqiuluo.shamrock.ui.service.DashboardInitializer
|
|
||||||
|
|
||||||
object FetchPortHandler: ModuleHandler() {
|
|
||||||
override val cmd: String = "success"
|
|
||||||
|
|
||||||
override fun onReceive(callbackId: Int, values: ContentValues, context: Context) {
|
|
||||||
AppRuntime.state.supportVoice.value = values.getAsBoolean("voice")
|
|
||||||
DashboardInitializer(context, values.getAsInteger("port"))
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,13 +3,37 @@ package moe.fuqiuluo.shamrock.ui.service.handlers
|
|||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import moe.fuqiuluo.shamrock.ui.app.AppRuntime
|
import moe.fuqiuluo.shamrock.ui.app.AppRuntime
|
||||||
import moe.fuqiuluo.shamrock.ui.app.ShamrockConfig
|
import moe.fuqiuluo.shamrock.app.config.ShamrockConfig
|
||||||
|
import moe.fuqiuluo.shamrock.config.*
|
||||||
|
|
||||||
internal object InitHandler: ModuleHandler() {
|
internal object InitHandler: ModuleHandler() {
|
||||||
override val cmd: String = "init"
|
override val cmd: String = "init"
|
||||||
|
|
||||||
override fun onReceive(callbackId: Int, values: ContentValues, context: Context) {
|
override fun onReceive(callbackId: Int, values: ContentValues, context: Context) {
|
||||||
|
update(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun update(context: Context) {
|
||||||
AppRuntime.log("推送QQ进程初始化设置数据包成功...")
|
AppRuntime.log("推送QQ进程初始化设置数据包成功...")
|
||||||
callback(context, callbackId, ShamrockConfig.getConfigMap(context))
|
|
||||||
|
val maps = hashMapOf<String, Any?>()
|
||||||
|
|
||||||
|
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)
|
||||||
|
ResourceGroup.update(context, maps)
|
||||||
|
RPCAddress.update(context, maps)
|
||||||
|
RPCPort.update(context, maps)
|
||||||
|
|
||||||
|
callback(context, 1, maps)
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified T> ConfigKey<T>.update(context: Context, map: HashMap<String, Any?>) {
|
||||||
|
map[name()] = ShamrockConfig[context, this]
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -29,6 +29,7 @@ abstract class ModuleHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
putExtra("__cmd", cmd)
|
||||||
putExtra("__hash", callbackId)
|
putExtra("__hash", callbackId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
package moe.fuqiuluo.shamrock.ui.service.handlers
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.content.Context
|
||||||
|
import android.widget.Toast
|
||||||
|
import moe.fuqiuluo.shamrock.tools.GlobalUi
|
||||||
|
import moe.fuqiuluo.shamrock.ui.app.AppRuntime
|
||||||
|
import java.util.Timer
|
||||||
|
import kotlin.concurrent.timer
|
||||||
|
import kotlin.concurrent.timerTask
|
||||||
|
|
||||||
|
object SwitchStatus: ModuleHandler() {
|
||||||
|
override val cmd: String
|
||||||
|
get() = "switch_status"
|
||||||
|
|
||||||
|
private var lastActiveTime = 0L
|
||||||
|
private var timer: Timer? = null
|
||||||
|
|
||||||
|
override fun onReceive(callbackId: Int, values: ContentValues, context: Context) {
|
||||||
|
val voiceSwitch = values.getAsBoolean("voice")
|
||||||
|
val nickname = values.getAsString("nickname")
|
||||||
|
val account = values.getAsString("account")
|
||||||
|
if (lastActiveTime == 0L) GlobalUi.post {
|
||||||
|
Toast.makeText(context, "激活成功", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
AppRuntime.state.apply {
|
||||||
|
isFined.value = true
|
||||||
|
coreVersion.value = values.getAsString("core_version")
|
||||||
|
coreName.value = "LSPosed"
|
||||||
|
supportVoice.value = voiceSwitch
|
||||||
|
}
|
||||||
|
AppRuntime.AccountInfo.apply {
|
||||||
|
uin.value = account
|
||||||
|
nick.value = nickname
|
||||||
|
}
|
||||||
|
lastActiveTime = System.currentTimeMillis()
|
||||||
|
startTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startTimer() {
|
||||||
|
timer?.cancel()
|
||||||
|
timer = timer("SwitchStatus", true, 0, 5_000) {
|
||||||
|
if (lastActiveTime != 0L && System.currentTimeMillis() - lastActiveTime > 30 * 1000) {
|
||||||
|
AppRuntime.state.isFined.value = false
|
||||||
|
lastActiveTime = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,9 +5,9 @@ import android.content.ContentValues
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.net.Uri
|
|
||||||
import moe.fuqiuluo.shamrock.ui.service.ModuleTalker
|
import moe.fuqiuluo.shamrock.ui.service.ModuleTalker
|
||||||
import moe.fuqiuluo.shamrock.ui.service.handlers.*
|
import moe.fuqiuluo.shamrock.ui.service.handlers.*
|
||||||
|
import android.net.Uri
|
||||||
|
|
||||||
class MultifunctionalProvider: ContentProvider() {
|
class MultifunctionalProvider: ContentProvider() {
|
||||||
override fun insert(uri: Uri, content: ContentValues?): Uri {
|
override fun insert(uri: Uri, content: ContentValues?): Uri {
|
||||||
@ -28,8 +28,8 @@ class MultifunctionalProvider: ContentProvider() {
|
|||||||
|
|
||||||
override fun onCreate(): Boolean {
|
override fun onCreate(): Boolean {
|
||||||
ModuleTalker.register(InitHandler)
|
ModuleTalker.register(InitHandler)
|
||||||
ModuleTalker.register(FetchPortHandler)
|
|
||||||
ModuleTalker.register(LogHandler)
|
ModuleTalker.register(LogHandler)
|
||||||
|
ModuleTalker.register(SwitchStatus)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ class MultifunctionalProvider: ContentProvider() {
|
|||||||
|
|
||||||
inline fun Context.broadcastToModule(intentBuilder: Intent.() -> Unit) {
|
inline fun Context.broadcastToModule(intentBuilder: Intent.() -> Unit) {
|
||||||
val intent = Intent()
|
val intent = Intent()
|
||||||
intent.action = "moe.fuqiuluo.xqbot.dynamic"
|
intent.action = "moe.fuqiuluo.kritor.dynamic"
|
||||||
intent.intentBuilder()
|
intent.intentBuilder()
|
||||||
sendBroadcast(intent)
|
sendBroadcast(intent)
|
||||||
}
|
}
|
@ -1,23 +1,35 @@
|
|||||||
package moe.fuqiuluo.shamrock.ui.tools
|
package moe.fuqiuluo.shamrock.ui.tools
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.animateColor
|
import androidx.compose.animation.animateColor
|
||||||
|
import androidx.compose.animation.core.FastOutLinearInEasing
|
||||||
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||||
import androidx.compose.animation.core.LinearEasing
|
import androidx.compose.animation.core.LinearEasing
|
||||||
|
import androidx.compose.animation.core.MutableTransitionState
|
||||||
|
import androidx.compose.animation.core.TweenSpec
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.core.updateTransition
|
import androidx.compose.animation.core.updateTransition
|
||||||
|
import androidx.compose.animation.scaleIn
|
||||||
|
import androidx.compose.animation.scaleOut
|
||||||
import androidx.compose.foundation.Indication
|
import androidx.compose.foundation.Indication
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.indication
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.selection.selectable
|
import androidx.compose.foundation.selection.selectable
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ProvideTextStyle
|
import androidx.compose.material3.ProvideTextStyle
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.Typography
|
import androidx.compose.material3.Typography
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
@ -31,8 +43,10 @@ import androidx.compose.ui.layout.LastBaseline
|
|||||||
import androidx.compose.ui.layout.Layout
|
import androidx.compose.ui.layout.Layout
|
||||||
import androidx.compose.ui.layout.Placeable
|
import androidx.compose.ui.layout.Placeable
|
||||||
import androidx.compose.ui.layout.layoutId
|
import androidx.compose.ui.layout.layoutId
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.Density
|
import androidx.compose.ui.unit.Density
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@ -135,20 +149,18 @@ private fun TabBaselineLayout(
|
|||||||
text: @Composable (() -> Unit)?,
|
text: @Composable (() -> Unit)?,
|
||||||
icon: @Composable (() -> Unit)?
|
icon: @Composable (() -> Unit)?
|
||||||
) {
|
) {
|
||||||
Layout(
|
Layout({
|
||||||
{
|
if (text != null) {
|
||||||
if (text != null) {
|
Box(
|
||||||
Box(
|
Modifier
|
||||||
Modifier
|
.layoutId("text")
|
||||||
.layoutId("text")
|
.padding(horizontal = HorizontalTextPadding)
|
||||||
.padding(horizontal = HorizontalTextPadding)
|
) { text() }
|
||||||
) { text() }
|
|
||||||
}
|
|
||||||
if (icon != null) {
|
|
||||||
Box(Modifier.layoutId("icon")) { icon() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
) { measurables, constraints ->
|
if (icon != null) {
|
||||||
|
Box(Modifier.layoutId("icon")) { icon() }
|
||||||
|
}
|
||||||
|
}) { measurables, constraints ->
|
||||||
val textPlaceable = text?.let {
|
val textPlaceable = text?.let {
|
||||||
measurables.first { it.layoutId == "text" }.measure(
|
measurables.first { it.layoutId == "text" }.measure(
|
||||||
// Measure with loose constraints for height as we don't want the text to take up more
|
// Measure with loose constraints for height as we don't want the text to take up more
|
||||||
@ -247,21 +259,66 @@ fun ShamrockTab(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
text: @Composable (() -> Unit)? = null,
|
|
||||||
icon: @Composable (() -> Unit)? = null,
|
|
||||||
selectedContentColor: Color = GlobalColor.TabSelected,
|
selectedContentColor: Color = GlobalColor.TabSelected,
|
||||||
unselectedContentColor: Color = selectedContentColor,
|
unselectedContentColor: Color = selectedContentColor,
|
||||||
indication: Indication? = rememberRipple(bounded = true, color = selectedContentColor),
|
indication: Indication? = rememberRipple(bounded = true, color = selectedContentColor),
|
||||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
|
titleWithIcon: Pair<String, Int>,
|
||||||
|
visibleState: MutableTransitionState<Boolean>
|
||||||
) {
|
) {
|
||||||
val styledText: @Composable (() -> Unit)? = text?.let {
|
var text: @Composable (() -> Unit)? = null
|
||||||
@Composable {
|
var icon: @Composable (() -> Unit)? = null
|
||||||
val style =
|
|
||||||
MaterialTheme.typography.fromToken(PrimaryNavigationTabTokens.LabelTextFont)
|
if (!selected) {
|
||||||
.copy(textAlign = TextAlign.Center)
|
icon = {
|
||||||
ProvideTextStyle(style, content = text)
|
Icon(
|
||||||
|
painter = painterResource(id = titleWithIcon.second),
|
||||||
|
contentDescription = titleWithIcon.first,
|
||||||
|
tint = Color.Unspecified,
|
||||||
|
modifier = Modifier
|
||||||
|
.height(24.dp)
|
||||||
|
.width(24.dp)
|
||||||
|
.padding(bottom = 5.dp)
|
||||||
|
.indication(
|
||||||
|
remember { MutableInteractionSource() },
|
||||||
|
rememberRipple(color = Color.Transparent)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
text = {
|
||||||
|
val style = MaterialTheme.typography
|
||||||
|
.fromToken(PrimaryNavigationTabTokens.LabelTextFont)
|
||||||
|
.copy(textAlign = TextAlign.Center)
|
||||||
|
|
||||||
|
ProvideTextStyle(style) {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visibleState = visibleState,
|
||||||
|
enter = remember {
|
||||||
|
scaleIn(animationSpec = TweenSpec(150, easing = FastOutLinearInEasing))
|
||||||
|
},
|
||||||
|
exit = remember {
|
||||||
|
scaleOut(animationSpec = TweenSpec(150, easing = FastOutSlowInEasing))
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = titleWithIcon.first,
|
||||||
|
color = GlobalColor.TabItem,
|
||||||
|
fontSize = 15.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(bottom = 5.dp)
|
||||||
|
.indication(
|
||||||
|
remember { MutableInteractionSource() },
|
||||||
|
rememberRipple(color = Color.Transparent)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ShamrockTab(
|
ShamrockTab(
|
||||||
selected,
|
selected,
|
||||||
onClick,
|
onClick,
|
||||||
@ -272,7 +329,10 @@ fun ShamrockTab(
|
|||||||
interactionSource,
|
interactionSource,
|
||||||
indication
|
indication
|
||||||
) {
|
) {
|
||||||
TabBaselineLayout(icon = icon, text = styledText)
|
TabBaselineLayout(
|
||||||
|
icon = icon,
|
||||||
|
text = text
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,16 +2,14 @@ package moe.fuqiuluo.shamrock
|
|||||||
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
import org.junit.Assert.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Example local unit test, which will execute on the development machine (host).
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
*
|
*
|
||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
* See [testing documentation](http://d.android.com/tools/testing).
|
||||||
*/
|
*/
|
||||||
class ExampleUnitTest {
|
class ExampleUnitTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun addition_isCorrect() {
|
fun test() {
|
||||||
assertEquals(4, 2 + 2)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "1.9.21"
|
kotlin("jvm") version "1.9.22"
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
@ -7,10 +7,6 @@ val DEPENDENCY_ANDROIDX = arrayOf(
|
|||||||
"androidx.activity:activity-compose:1.7.2",
|
"androidx.activity:activity-compose:1.7.2",
|
||||||
)
|
)
|
||||||
|
|
||||||
const val DEPENDENCY_JSON5K = "io.github.xn32:json5k:0.3.0"
|
|
||||||
const val DEPENDENCY_PROTOBUF = "com.google.protobuf:protobuf-java:3.24.0"
|
|
||||||
const val DEPENDENCY_JAVA_WEBSOCKET = "org.java-websocket:Java-WebSocket:1.5.4"
|
|
||||||
|
|
||||||
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"
|
||||||
@ -19,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"
|
||||||
}
|
}
|
42
kritor/.gitignore
vendored
Normal file
42
kritor/.gitignore
vendored
Normal 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
76
kritor/build.gradle.kts
Normal 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"
|
||||||
|
}
|
0
kritor/consumer-rules.pro
Normal file
0
kritor/consumer-rules.pro
Normal file
1
kritor/kritor
Submodule
1
kritor/kritor
Submodule
Submodule kritor/kritor added at 27669a8f57
21
kritor/proguard-rules.pro
vendored
Normal file
21
kritor/proguard-rules.pro
vendored
Normal 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
|
@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
id("com.google.devtools.ksp") version "1.9.21-1.0.15"
|
id("com.google.devtools.ksp") version "1.9.22-1.0.17"
|
||||||
kotlin("plugin.serialization") version "1.9.21"
|
kotlin("plugin.serialization") version "1.9.22"
|
||||||
}
|
}
|
||||||
|
|
||||||
ksp {
|
ksp {
|
||||||
@ -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")
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
@ -1,85 +0,0 @@
|
|||||||
@file:OptIn(KspExperimental::class)
|
|
||||||
@file:Suppress("LocalVariableName", "UNCHECKED_CAST")
|
|
||||||
|
|
||||||
package moe.fuqiuluo.ksp.impl
|
|
||||||
|
|
||||||
import com.google.devtools.ksp.KspExperimental
|
|
||||||
import com.google.devtools.ksp.getAnnotationsByType
|
|
||||||
import com.google.devtools.ksp.getClassDeclarationByName
|
|
||||||
import com.google.devtools.ksp.getKotlinClassByName
|
|
||||||
import com.google.devtools.ksp.processing.CodeGenerator
|
|
||||||
import com.google.devtools.ksp.processing.Dependencies
|
|
||||||
import com.google.devtools.ksp.processing.KSPLogger
|
|
||||||
import com.google.devtools.ksp.processing.Resolver
|
|
||||||
import com.google.devtools.ksp.processing.SymbolProcessor
|
|
||||||
import com.google.devtools.ksp.symbol.ClassKind
|
|
||||||
import com.google.devtools.ksp.symbol.KSAnnotated
|
|
||||||
import com.google.devtools.ksp.symbol.KSClassDeclaration
|
|
||||||
import com.google.devtools.ksp.symbol.KSVisitorVoid
|
|
||||||
import com.google.devtools.ksp.validate
|
|
||||||
import com.squareup.kotlinpoet.FileSpec
|
|
||||||
import com.squareup.kotlinpoet.FunSpec
|
|
||||||
import moe.fuqiuluo.symbols.OneBotHandler
|
|
||||||
|
|
||||||
class OneBotHandlerProcessor(
|
|
||||||
private val codeGenerator: CodeGenerator,
|
|
||||||
private val logger: KSPLogger
|
|
||||||
): SymbolProcessor {
|
|
||||||
override fun process(resolver: Resolver): List<KSAnnotated> {
|
|
||||||
val ActionManagerNode = resolver.getClassDeclarationByName("moe.fuqiuluo.shamrock.remote.action.ActionManager")
|
|
||||||
?: resolver.getKotlinClassByName("moe.fuqiuluo.shamrock.remote.action.ActionManager")
|
|
||||||
?: resolver.getClassDeclarationByName("ActionManager")
|
|
||||||
val symbols = resolver.getSymbolsWithAnnotation(OneBotHandler::class.qualifiedName!!)
|
|
||||||
val unableToProcess = symbols.filterNot { it.validate() }
|
|
||||||
if (ActionManagerNode != null) {
|
|
||||||
val oneBotHandlers = (symbols.filter {
|
|
||||||
it is KSClassDeclaration && it.validate() && it.classKind == ClassKind.OBJECT
|
|
||||||
} as Sequence<KSClassDeclaration>).toList()
|
|
||||||
|
|
||||||
if (oneBotHandlers.isNotEmpty()) {
|
|
||||||
ActionManagerNode.accept(ActionManagerVisitor(oneBotHandlers), Unit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return unableToProcess.toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class ActionManagerVisitor(
|
|
||||||
private val actionHandlers: List<KSClassDeclaration>
|
|
||||||
): KSVisitorVoid() {
|
|
||||||
override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {
|
|
||||||
val packageName = classDeclaration.packageName.asString()
|
|
||||||
|
|
||||||
// generate kotlin `init { }`
|
|
||||||
val fileSpec = FileSpec.builder(packageName, classDeclaration.qualifiedName?.asString() ?: run {
|
|
||||||
throw IllegalStateException("ActionManagerVisitor: classDeclaration.qualifiedName is null")
|
|
||||||
}).addFunction(FunSpec.builder("initManager").apply {
|
|
||||||
actionHandlers.forEach { handler ->
|
|
||||||
// fetch the params of the annotation
|
|
||||||
val annotation = handler.getAnnotationsByType(OneBotHandler::class).first()
|
|
||||||
val actionName = annotation.actionName
|
|
||||||
val alias = annotation.alias
|
|
||||||
alias.forEach { name ->
|
|
||||||
addStatement("actionMap[\"$name\"] = ${handler.simpleName.asString()}")
|
|
||||||
}
|
|
||||||
addStatement("actionMap[\"$actionName\"] = ${handler.simpleName.asString()}")
|
|
||||||
}
|
|
||||||
}.build()).apply {
|
|
||||||
addImport("moe.fuqiuluo.shamrock.remote.action.ActionManager", "actionMap")
|
|
||||||
actionHandlers.forEach {
|
|
||||||
addImport(it.packageName.asString(), it.simpleName.asString())
|
|
||||||
}
|
|
||||||
}.build()
|
|
||||||
|
|
||||||
codeGenerator.createNewFile(
|
|
||||||
dependencies = Dependencies(aggregating = false),
|
|
||||||
packageName = packageName,
|
|
||||||
fileName = "Auto" + classDeclaration.simpleName.asString()
|
|
||||||
).use { outputStream ->
|
|
||||||
outputStream.writer().use {
|
|
||||||
fileSpec.writeTo(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -32,7 +32,7 @@ class ProtobufProcessor(
|
|||||||
}.toList()
|
}.toList()
|
||||||
|
|
||||||
if (actions.isNotEmpty()) {
|
if (actions.isNotEmpty()) {
|
||||||
actions.forEachIndexed { index, clz ->
|
actions.forEachIndexed { _, clz ->
|
||||||
if (clz.isInternal()) return@forEachIndexed
|
if (clz.isInternal()) return@forEachIndexed
|
||||||
if (clz.isPrivate()) return@forEachIndexed
|
if (clz.isPrivate()) return@forEachIndexed
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
@file:Suppress("UNCHECKED_CAST", "LocalVariableName", "PrivatePropertyName")
|
@file:Suppress("UNCHECKED_CAST", "LocalVariableName", "PrivatePropertyName")
|
||||||
@file:OptIn(KspExperimental::class)
|
@file:OptIn(KspExperimental::class, KspExperimental::class)
|
||||||
|
|
||||||
package moe.fuqiuluo.ksp.impl
|
package moe.fuqiuluo.ksp.impl
|
||||||
|
|
||||||
@ -27,10 +27,14 @@ class XposedHookProcessor(
|
|||||||
private val logger: KSPLogger
|
private val logger: KSPLogger
|
||||||
): SymbolProcessor {
|
): SymbolProcessor {
|
||||||
override fun process(resolver: Resolver): List<KSAnnotated> {
|
override fun process(resolver: Resolver): List<KSAnnotated> {
|
||||||
val symbols = resolver.getSymbolsWithAnnotation(XposedHook::class.qualifiedName!!)
|
val symbols = resolver.getSymbolsWithAnnotation(
|
||||||
|
annotationName = XposedHook::class.qualifiedName!!,
|
||||||
|
inDepth = true
|
||||||
|
)
|
||||||
|
logger.warn("Found ${symbols.count()} classes annotated with XposedHook")
|
||||||
val unableToProcess = symbols.filterNot { it.validate() }
|
val unableToProcess = symbols.filterNot { it.validate() }
|
||||||
val actions = (symbols.filter {
|
val actions = (symbols.filter {
|
||||||
it is KSClassDeclaration && it.validate() && it.classKind == ClassKind.CLASS
|
it is KSClassDeclaration && it.classKind == ClassKind.CLASS
|
||||||
} as Sequence<KSClassDeclaration>).toList()
|
} as Sequence<KSClassDeclaration>).toList()
|
||||||
|
|
||||||
if (actions.isNotEmpty()) {
|
if (actions.isNotEmpty()) {
|
||||||
@ -46,7 +50,7 @@ class XposedHookProcessor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val context = ClassName("android.content", "Context")
|
val context = ClassName("android.content", "Context")
|
||||||
val packageName = "moe.fuqiuluo.shamrock.xposed.hooks"
|
val packageName = "moe.fuqiuluo.shamrock.xposed.actions"
|
||||||
val fileSpec = FileSpec.builder(packageName, "AutoActionLoader").addFunction(FunSpec.builder("runFirstActions")
|
val fileSpec = FileSpec.builder(packageName, "AutoActionLoader").addFunction(FunSpec.builder("runFirstActions")
|
||||||
.addParameter("ctx", context)
|
.addParameter("ctx", context)
|
||||||
.apply {
|
.apply {
|
||||||
@ -96,16 +100,6 @@ class XposedHookProcessor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return unableToProcess.toList()
|
return unableToProcess.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ActionLoaderVisitor(
|
|
||||||
private val firstActions: List<KSClassDeclaration>,
|
|
||||||
private val serviceActions: List<KSClassDeclaration>,
|
|
||||||
): KSVisitorVoid() {
|
|
||||||
override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -4,12 +4,12 @@ import com.google.auto.service.AutoService
|
|||||||
import com.google.devtools.ksp.processing.SymbolProcessor
|
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||||
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
|
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
|
||||||
import com.google.devtools.ksp.processing.SymbolProcessorProvider
|
import com.google.devtools.ksp.processing.SymbolProcessorProvider
|
||||||
import moe.fuqiuluo.ksp.impl.OneBotHandlerProcessor
|
import moe.fuqiuluo.ksp.impl.GrpcProcessor
|
||||||
|
|
||||||
@AutoService(SymbolProcessorProvider::class)
|
@AutoService(SymbolProcessorProvider::class)
|
||||||
class OneBotHandlerProcessorProvider: SymbolProcessorProvider {
|
class GrpcProvider: SymbolProcessorProvider {
|
||||||
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
|
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
|
||||||
return OneBotHandlerProcessor(
|
return GrpcProcessor(
|
||||||
environment.codeGenerator,
|
environment.codeGenerator,
|
||||||
environment.logger
|
environment.logger
|
||||||
)
|
)
|
@ -1,8 +1,10 @@
|
|||||||
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.library")
|
id("com.android.library")
|
||||||
id("org.jetbrains.kotlin.android")
|
id("org.jetbrains.kotlin.android")
|
||||||
kotlin("plugin.serialization") version "1.9.21"
|
kotlin("plugin.serialization") version "1.9.22"
|
||||||
id("com.google.devtools.ksp") version "1.9.21-1.0.15"
|
id("com.google.devtools.ksp") version "1.9.22-1.0.17"
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@ -35,11 +37,14 @@ 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"))
|
||||||
|
|
||||||
implementation(project(":annotations"))
|
implementation(project(":annotations"))
|
||||||
|
|
||||||
ksp(project(":processor"))
|
ksp(project(":processor"))
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<KotlinCompile>().configureEach {
|
||||||
|
kotlinOptions.freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
|
||||||
}
|
}
|
@ -32,6 +32,11 @@ data class AdaptShareInfoReq(
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Template(
|
data class Template(
|
||||||
@ProtoNumber(1) var templateId: UInt? = null,
|
@ProtoNumber(1) var templateId: ULong? = null,
|
||||||
@ProtoNumber(2) var templateData: ByteArray? = null,
|
@ProtoNumber(2) var templateData: ByteArray? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AdaptShareInfoResp(
|
||||||
|
@ProtoNumber(2) var json: String? = null,
|
||||||
|
): Protobuf<AdaptShareInfoResp>
|
@ -15,7 +15,7 @@ data class ContentHead(
|
|||||||
@ProtoNumber(8) val u6: Int? = null,
|
@ProtoNumber(8) val u6: Int? = null,
|
||||||
@ProtoNumber(9) val u7: Int? = null,
|
@ProtoNumber(9) val u7: Int? = null,
|
||||||
@ProtoNumber(11) val msgSeq: Long? = null,
|
@ProtoNumber(11) val msgSeq: Long? = null,
|
||||||
@ProtoNumber(12) val msgRandom: Long = Long.MIN_VALUE,
|
@ProtoNumber(12) val msgRandom: Long = Long.MIN_VALUE, // 0x0100000000000000L xor msgViaRandom
|
||||||
@ProtoNumber(14) val u4: Long? = null,
|
@ProtoNumber(14) val u4: Long? = null,
|
||||||
@ProtoNumber(15) val forwardHead: ForwardHead? = null,
|
@ProtoNumber(15) val forwardHead: ForwardHead? = null,
|
||||||
@ProtoNumber(28) val u5: Long? = null
|
@ProtoNumber(28) val u5: Long? = null
|
||||||
|
@ -7,9 +7,9 @@ import kotlinx.serialization.protobuf.ProtoNumber
|
|||||||
data class Ptt(
|
data class Ptt(
|
||||||
@ProtoNumber(1) var fileType: UInt?=null,
|
@ProtoNumber(1) var fileType: UInt?=null,
|
||||||
@ProtoNumber(2) var srcUin: ULong?=null,
|
@ProtoNumber(2) var srcUin: ULong?=null,
|
||||||
@ProtoNumber(3) var fileUuid: ByteArray?=null,
|
@ProtoNumber(3) var fileUuid: String?=null,
|
||||||
@ProtoNumber(4) var fileMd5: ByteArray?=null,
|
@ProtoNumber(4) var fileMd5: ByteArray?=null,
|
||||||
@ProtoNumber(5) var fileName: ByteArray?=null,
|
@ProtoNumber(5) var fileName: String?=null,
|
||||||
@ProtoNumber(6) var fileSize: UInt?=null,
|
@ProtoNumber(6) var fileSize: UInt?=null,
|
||||||
@ProtoNumber(7) var reserve: ByteArray?=null,
|
@ProtoNumber(7) var reserve: ByteArray?=null,
|
||||||
@ProtoNumber(8) var fileId: UInt?=null,
|
@ProtoNumber(8) var fileId: UInt?=null,
|
||||||
@ -22,11 +22,19 @@ data class Ptt(
|
|||||||
@ProtoNumber(15) var magicPttIndex: UInt?=null,
|
@ProtoNumber(15) var magicPttIndex: UInt?=null,
|
||||||
@ProtoNumber(16) var voiceSwitch: UInt?=null,
|
@ProtoNumber(16) var voiceSwitch: UInt?=null,
|
||||||
@ProtoNumber(17) var pttUrl: ByteArray?=null,
|
@ProtoNumber(17) var pttUrl: ByteArray?=null,
|
||||||
@ProtoNumber(18) var groupFileKey: ByteArray?=null,
|
@ProtoNumber(18) var groupFileKey: String?=null,
|
||||||
@ProtoNumber(19) var time: UInt?=null,
|
@ProtoNumber(19) var time: UInt?=null,
|
||||||
@ProtoNumber(20) var downPara: ByteArray?=null,
|
@ProtoNumber(20) var downPara: ByteArray?=null,
|
||||||
@ProtoNumber(29) var format: UInt?=null,
|
@ProtoNumber(29) var format: UInt?=null,
|
||||||
@ProtoNumber(30) var pbReserve: ByteArray?=null,
|
@ProtoNumber(30) var pbReserve: PbReserve?=null,
|
||||||
@ProtoNumber(31) var rptPttUrls: List<String>? = null,
|
@ProtoNumber(31) var rptPttUrls: List<String>? = null,
|
||||||
@ProtoNumber(32) var downloadFlag: UInt?=null,
|
@ProtoNumber(32) var downloadFlag: UInt?=null,
|
||||||
)
|
){
|
||||||
|
companion object{
|
||||||
|
@Serializable
|
||||||
|
data class PbReserve(
|
||||||
|
@ProtoNumber(2) var magic: Int?=null,
|
||||||
|
@ProtoNumber(7) var reserve: Int?=null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -9,9 +9,9 @@ import kotlinx.serialization.protobuf.ProtoNumber
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class RichText(
|
data class RichText(
|
||||||
@ProtoNumber(1) val attr: Attr? = null,
|
@ProtoNumber(1) val attr: Attr? = null,
|
||||||
@ProtoNumber(2) val elements: List<Elem>? = null,
|
@ProtoNumber(2) var elements: List<Elem>? = null,
|
||||||
@ProtoNumber(3) val not_online_file: NotOnlineFile? = null,
|
@ProtoNumber(3) var not_online_file: NotOnlineFile? = null,
|
||||||
@ProtoNumber(4) val ptt: Ptt? = null,
|
@ProtoNumber(4) var ptt: Ptt? = null,
|
||||||
@ProtoNumber(5) val tmp_ptt: TmpPtt? = null,
|
@ProtoNumber(5) val tmp_ptt: TmpPtt? = null,
|
||||||
@ProtoNumber(6) val trans_211_tmp_msg: Trans211TmpMsg? = null,
|
@ProtoNumber(6) val trans_211_tmp_msg: Trans211TmpMsg? = null,
|
||||||
)
|
)
|
||||||
|
@ -30,7 +30,7 @@ data class CustomFace(
|
|||||||
@ProtoNumber(23) var height: UInt? = null,
|
@ProtoNumber(23) var height: UInt? = null,
|
||||||
@ProtoNumber(24) var source: UInt? = null,
|
@ProtoNumber(24) var source: UInt? = null,
|
||||||
@ProtoNumber(25) var size: UInt? = null,
|
@ProtoNumber(25) var size: UInt? = null,
|
||||||
@ProtoNumber(26) var origin: UInt? = null,
|
@ProtoNumber(26) var origin: Boolean? = null,
|
||||||
@ProtoNumber(27) var thumbWidth: UInt? = null,
|
@ProtoNumber(27) var thumbWidth: UInt? = null,
|
||||||
@ProtoNumber(28) var thumbHeight: UInt? = null,
|
@ProtoNumber(28) var thumbHeight: UInt? = null,
|
||||||
@ProtoNumber(29) var showLen: UInt? = null,
|
@ProtoNumber(29) var showLen: UInt? = null,
|
||||||
@ -39,11 +39,26 @@ data class CustomFace(
|
|||||||
@ProtoNumber(32) var width400: UInt? = null,
|
@ProtoNumber(32) var width400: UInt? = null,
|
||||||
@ProtoNumber(33) var height400: UInt? = null,
|
@ProtoNumber(33) var height400: UInt? = null,
|
||||||
@ProtoNumber(34) var pbReserve: PbReserve? = null,
|
@ProtoNumber(34) var pbReserve: PbReserve? = null,
|
||||||
){
|
) {
|
||||||
companion object{
|
companion object {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PbReserve(
|
data class PbReserve(
|
||||||
@ProtoNumber(1) var field1: Int? = null
|
@ProtoNumber(1) var field1: Int? = null,
|
||||||
|
@ProtoNumber(3) var field3: Int? = null,
|
||||||
|
@ProtoNumber(4) var field4: Int? = null,
|
||||||
|
@ProtoNumber(10) var field10: Int? = null,
|
||||||
|
@ProtoNumber(21) var field21: Object1? = null,
|
||||||
|
@ProtoNumber(31) var field31: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Object1(
|
||||||
|
@ProtoNumber(1) var field1: Int? = null,
|
||||||
|
@ProtoNumber(2) var field2: String? = null,
|
||||||
|
@ProtoNumber(3) var field3: Int? = null,
|
||||||
|
@ProtoNumber(4) var field4: Int? = null,
|
||||||
|
@ProtoNumber(5) var field5: Int? = null,
|
||||||
|
@ProtoNumber(7) var md5Str: String? = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,8 +5,8 @@ import kotlinx.serialization.protobuf.ProtoNumber
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class FaceMsg(
|
data class FaceMsg(
|
||||||
@ProtoNumber(1) val id: Int? = null,
|
@ProtoNumber(1) val index: Int? = null,
|
||||||
@ProtoNumber(2) var old: ByteArray? = null,
|
@ProtoNumber(2) var old: ByteArray? = null,
|
||||||
@ProtoNumber(11) var buf: ByteArray? = null,
|
@ProtoNumber(11) var buf: ByteArray? = null,
|
||||||
|
|
||||||
)
|
)
|
@ -12,7 +12,7 @@ data class GeneralFlags(
|
|||||||
@ProtoNumber(4) val rpId: ByteArray? = null,
|
@ProtoNumber(4) val rpId: ByteArray? = null,
|
||||||
@ProtoNumber(5) val prpFold: UInt? = null,
|
@ProtoNumber(5) val prpFold: UInt? = null,
|
||||||
@ProtoNumber(6) val longTextFlag: UInt? = null,
|
@ProtoNumber(6) val longTextFlag: UInt? = null,
|
||||||
@ProtoNumber(7) val longTextResid: ByteArray? = null,
|
@ProtoNumber(7) val longTextResid: String? = null,
|
||||||
@ProtoNumber(8) val groupType: UInt? = null,
|
@ProtoNumber(8) val groupType: UInt? = null,
|
||||||
@ProtoNumber(9) val toUinFlag: UInt? = null,
|
@ProtoNumber(9) val toUinFlag: UInt? = null,
|
||||||
@ProtoNumber(10) val glamourLevel: UInt? = null,
|
@ProtoNumber(10) val glamourLevel: UInt? = null,
|
||||||
|
@ -5,19 +5,19 @@ import kotlinx.serialization.protobuf.ProtoNumber
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class NotOnlineImage(
|
data class NotOnlineImage(
|
||||||
@ProtoNumber(1) val filePath: ByteArray? = null,
|
@ProtoNumber(1) val filePath: String? = null,
|
||||||
@ProtoNumber(2) val fileLen: UInt? = null,
|
@ProtoNumber(2) val fileLen: UInt? = null,
|
||||||
@ProtoNumber(3) val downloadPath: ByteArray? = null,
|
@ProtoNumber(3) val downloadPath: String? = null,
|
||||||
@ProtoNumber(4) val oldVerSendFile: ByteArray? = null,
|
@ProtoNumber(4) val oldVerSendFile: ByteArray? = null,
|
||||||
@ProtoNumber(5) val imgType: UInt? = null,
|
@ProtoNumber(5) val imgType: UInt? = null,
|
||||||
@ProtoNumber(6) val previewsImage: ByteArray? = null,
|
@ProtoNumber(6) val previewsImage: ByteArray? = null,
|
||||||
@ProtoNumber(7) val picMd5: ByteArray? = null,
|
@ProtoNumber(7) val picMd5: ByteArray? = null,
|
||||||
@ProtoNumber(8) val picHeight: UInt? = null,
|
@ProtoNumber(8) val picHeight: UInt? = null,
|
||||||
@ProtoNumber(9) val picWidth: UInt? = null,
|
@ProtoNumber(9) val picWidth: UInt? = null,
|
||||||
@ProtoNumber(10) val resId: ByteArray? = null,
|
@ProtoNumber(10) val resId: String? = null, // md5 + ".jpg"
|
||||||
@ProtoNumber(11) val flag: ByteArray? = null,
|
@ProtoNumber(11) val flag: ByteArray? = null,
|
||||||
@ProtoNumber(12) val thumbUrl: String? = null,
|
@ProtoNumber(12) val thumbUrl: String? = null,
|
||||||
@ProtoNumber(13) val original: UInt? = null,
|
@ProtoNumber(13) val original: Boolean? = null,
|
||||||
@ProtoNumber(14) val bigUrl: String? = null,
|
@ProtoNumber(14) val bigUrl: String? = null,
|
||||||
@ProtoNumber(15) val origUrl: String? = null,
|
@ProtoNumber(15) val origUrl: String? = null,
|
||||||
@ProtoNumber(16) val bizType: UInt? = null,
|
@ProtoNumber(16) val bizType: UInt? = null,
|
||||||
@ -39,8 +39,23 @@ data class NotOnlineImage(
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class PbReserve(
|
data class PbReserve(
|
||||||
@ProtoNumber(1) var field1: Int? = null,
|
@ProtoNumber(1) var field1: Int? = null,
|
||||||
|
@ProtoNumber(3) var field3: Int? = null,
|
||||||
|
@ProtoNumber(4) var field4: Int? = null,
|
||||||
@ProtoNumber(8) var field8: String? = null,
|
@ProtoNumber(8) var field8: String? = null,
|
||||||
@ProtoNumber(30) var url: String? = null
|
@ProtoNumber(10) var field10: Int? = null,
|
||||||
|
@ProtoNumber(20) var field20: Object1? = null,
|
||||||
|
@ProtoNumber(30) var url: String? = null,
|
||||||
|
@ProtoNumber(31) var md5Str: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Object1(
|
||||||
|
@ProtoNumber(1) var field1: Int? = null,
|
||||||
|
@ProtoNumber(2) var field2: String? = null,
|
||||||
|
@ProtoNumber(3) var field3: Int? = null,
|
||||||
|
@ProtoNumber(4) var field4: Int? = null,
|
||||||
|
@ProtoNumber(5) var field5: Int? = null,
|
||||||
|
@ProtoNumber(7) var field7: String? = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -21,7 +21,7 @@ data class SourceMsg(
|
|||||||
companion object {
|
companion object {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PbReserve(
|
data class PbReserve(
|
||||||
@ProtoNumber(3) var field3: ULong? = null,
|
@ProtoNumber(3) var msgRand: ULong? = null,
|
||||||
@ProtoNumber(6) var senderUid: String? = null,
|
@ProtoNumber(6) var senderUid: String? = null,
|
||||||
@ProtoNumber(7) var receiverUid: String? = null,
|
@ProtoNumber(7) var receiverUid: String? = null,
|
||||||
@ProtoNumber(8) var field8: Int? = null,
|
@ProtoNumber(8) var field8: Int? = null,
|
||||||
|
@ -10,5 +10,12 @@ data class TextMsg(
|
|||||||
@ProtoNumber(3) val attr6Buf: ByteArray? = null,
|
@ProtoNumber(3) val attr6Buf: ByteArray? = null,
|
||||||
@ProtoNumber(4) val attr7Buf: ByteArray? = null,
|
@ProtoNumber(4) val attr7Buf: ByteArray? = null,
|
||||||
@ProtoNumber(11) val buf: ByteArray? = null,
|
@ProtoNumber(11) val buf: ByteArray? = null,
|
||||||
@ProtoNumber(12) val pbReserve: ByteArray? = null,
|
@ProtoNumber(12) val pbReserve: PbReserve? = null,
|
||||||
)
|
){
|
||||||
|
companion object {
|
||||||
|
@Serializable
|
||||||
|
data class PbReserve(
|
||||||
|
@ProtoNumber(1) val field1: String? = null, // [打 call]] 请使用最新版手机 QQ 体验新功能
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
@ -23,7 +23,7 @@ data class Row(
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Button(
|
data class Button(
|
||||||
@ProtoNumber(1) val id: Int? = null,
|
@ProtoNumber(1) val id: String? = null,
|
||||||
@ProtoNumber(2) val renderData: RenderData? = null,
|
@ProtoNumber(2) val renderData: RenderData? = null,
|
||||||
@ProtoNumber(3) val action: Action? = null,
|
@ProtoNumber(3) val action: Action? = null,
|
||||||
)
|
)
|
||||||
@ -41,8 +41,8 @@ data class Action(
|
|||||||
@ProtoNumber(2) val permission: Permission? = null,
|
@ProtoNumber(2) val permission: Permission? = null,
|
||||||
@ProtoNumber(4) val unsupportTips: String? = null,
|
@ProtoNumber(4) val unsupportTips: String? = null,
|
||||||
@ProtoNumber(5) val data: String? = null,
|
@ProtoNumber(5) val data: String? = null,
|
||||||
@ProtoNumber(6) val reply: Boolean? = null,
|
@ProtoNumber(7) val reply: Boolean? = null,
|
||||||
@ProtoNumber(7) val enter: Boolean? = null,
|
@ProtoNumber(8) val enter: Boolean? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -11,7 +11,7 @@ data class QFaceExtra(
|
|||||||
@ProtoNumber(3) val faceId: Int? = null,
|
@ProtoNumber(3) val faceId: Int? = null,
|
||||||
@ProtoNumber(4) val field4: Int? = null,
|
@ProtoNumber(4) val field4: Int? = null,
|
||||||
@ProtoNumber(5) val field5: Int? = null,
|
@ProtoNumber(5) val field5: Int? = null,
|
||||||
@ProtoNumber(6) val field6: String? = null,
|
@ProtoNumber(6) val result: String? = null,
|
||||||
@ProtoNumber(7) val faceText: String? = null,
|
@ProtoNumber(7) val faceText: String? = null,
|
||||||
@ProtoNumber(9) val field9: Int? = null
|
@ProtoNumber(9) val field9: Int? = null
|
||||||
) : Protobuf<QFaceExtra>
|
) : Protobuf<QFaceExtra>
|
||||||
|
@ -31,7 +31,7 @@ data class RecvLongMsgInfo(
|
|||||||
data class SendLongMsgInfo(
|
data class SendLongMsgInfo(
|
||||||
@ProtoNumber(1) val type: Int? = null,
|
@ProtoNumber(1) val type: Int? = null,
|
||||||
@ProtoNumber(2) val uid: LongMsgUid? = null,
|
@ProtoNumber(2) val uid: LongMsgUid? = null,
|
||||||
@ProtoNumber(3) val groupUin: Int? = null,
|
@ProtoNumber(3) val groupUin: ULong? = null,
|
||||||
@ProtoNumber(4) val payload: ByteArray? = null,
|
@ProtoNumber(4) val payload: ByteArray? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,55 +1 @@
|
|||||||
@file:OptIn(ExperimentalSerializationApi::class)
|
|
||||||
package protobuf.message.multimedia
|
package protobuf.message.multimedia
|
||||||
|
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.protobuf.ProtoNumber
|
|
||||||
import moe.fuqiuluo.symbols.Protobuf
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class RichMediaForPicData(
|
|
||||||
@ProtoNumber(1) val info: MediaInfo?,
|
|
||||||
@ProtoNumber(2) val display: DisplayMediaInfo?,
|
|
||||||
): Protobuf<RichMediaForPicData> {
|
|
||||||
companion object {
|
|
||||||
@Serializable
|
|
||||||
data class MediaInfo(
|
|
||||||
@ProtoNumber(1) val picture: Picture? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Picture(
|
|
||||||
@ProtoNumber(1) val info: PictureInfo? = null,
|
|
||||||
@ProtoNumber(2) val fileId: String? = null,
|
|
||||||
@ProtoNumber(4) val time: ULong? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class PictureInfo(
|
|
||||||
@ProtoNumber(2) val md5Hex: String? = null,
|
|
||||||
@ProtoNumber(3) val sha: String? = null,
|
|
||||||
@ProtoNumber(4) val name: String? = null,
|
|
||||||
@ProtoNumber(6) val width: Int? = null,
|
|
||||||
@ProtoNumber(7) val height: Int? = null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class DisplayMediaInfo(
|
|
||||||
@ProtoNumber(1) val show: Show? = null,
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
@Serializable
|
|
||||||
data class Show(
|
|
||||||
@ProtoNumber(2) val text: String? = null,
|
|
||||||
@ProtoNumber(12) val download: Download? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Download(
|
|
||||||
@ProtoNumber(30) val url: String? = null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -49,8 +49,8 @@ data class UploadCompletedReq(
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class MsgInfo(
|
data class MsgInfo(
|
||||||
@ProtoNumber(1) val msgInfoBody: List<MsgInfoBody>,
|
@ProtoNumber(1) val msgInfoBody: List<MsgInfoBody>,
|
||||||
@ProtoNumber(2) val extBizInfo: ExtBizInfo,
|
@ProtoNumber(2) val extBizInfo: ExtBizInfo?,
|
||||||
)
|
): Protobuf<MsgInfo>
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class MsgInfoBody(
|
data class MsgInfoBody(
|
||||||
@ -106,7 +106,7 @@ data class UploadReq(
|
|||||||
@ProtoNumber(5) val compatQMsgSceneType: UInt? = null,
|
@ProtoNumber(5) val compatQMsgSceneType: UInt? = null,
|
||||||
@ProtoNumber(6) val extBizInfo: ExtBizInfo? = null,
|
@ProtoNumber(6) val extBizInfo: ExtBizInfo? = null,
|
||||||
@ProtoNumber(7) val clientSeq: UInt? = null,
|
@ProtoNumber(7) val clientSeq: UInt? = null,
|
||||||
@ProtoNumber(8) val noNeedCompatMsg: Boolean = false,
|
@ProtoNumber(8) val noNeedCompatMsg: Boolean? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -114,7 +114,7 @@ data class ExtBizInfo(
|
|||||||
@ProtoNumber(1) val pic: PicExtBizInfo? = null,
|
@ProtoNumber(1) val pic: PicExtBizInfo? = null,
|
||||||
@ProtoNumber(2) val video: VideoExtBizInfo? = null,
|
@ProtoNumber(2) val video: VideoExtBizInfo? = null,
|
||||||
@ProtoNumber(3) val ptt: PttExtBizInfo? = null,
|
@ProtoNumber(3) val ptt: PttExtBizInfo? = null,
|
||||||
@ProtoNumber(10) val busiType: UInt,
|
@ProtoNumber(10) val busiType: UInt?,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -132,15 +132,15 @@ data class PttExtBizInfo(
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class VideoExtBizInfo(
|
data class VideoExtBizInfo(
|
||||||
@ProtoNumber(1) val fromScene: UInt,
|
@ProtoNumber(1) val fromScene: UInt?,
|
||||||
@ProtoNumber(2) val toScene: UInt,
|
@ProtoNumber(2) val toScene: UInt?,
|
||||||
@ProtoNumber(3) val bytesPbReserve: ByteArray,
|
@ProtoNumber(3) val bytesPbReserve: ByteArray?,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PicExtBizInfo(
|
data class PicExtBizInfo(
|
||||||
@ProtoNumber(1) val bizType: UInt,
|
@ProtoNumber(1) val bizType: UInt?,
|
||||||
@ProtoNumber(2) val textSummary: String,
|
@ProtoNumber(2) val textSummary: String?,
|
||||||
@ProtoNumber(11) val bytesPbReserveC2c: ByteArray? = null,
|
@ProtoNumber(11) val bytesPbReserveC2c: ByteArray? = null,
|
||||||
@ProtoNumber(12) val bytesPbReserveTroop: ByteArray? = null,
|
@ProtoNumber(12) val bytesPbReserveTroop: ByteArray? = null,
|
||||||
@ProtoNumber(1001) val fromScene: UInt? = null,
|
@ProtoNumber(1001) val fromScene: UInt? = null,
|
||||||
@ -156,15 +156,15 @@ data class UploadInfo(
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class FileInfo(
|
data class FileInfo(
|
||||||
@ProtoNumber(1) val fileSize: ULong,
|
@ProtoNumber(1) val fileSize: ULong?,
|
||||||
@ProtoNumber(2) val md5: String,
|
@ProtoNumber(2) val md5: String?,
|
||||||
@ProtoNumber(3) val sha1: String,
|
@ProtoNumber(3) val sha1: String?,
|
||||||
@ProtoNumber(4) val name: String,
|
@ProtoNumber(4) val name: String?,
|
||||||
@ProtoNumber(5) val fileType: FileType,
|
@ProtoNumber(5) val fileType: FileType?,
|
||||||
@ProtoNumber(6) val width: UInt,
|
@ProtoNumber(6) val width: UInt?,
|
||||||
@ProtoNumber(7) val height: UInt,
|
@ProtoNumber(7) val height: UInt?,
|
||||||
@ProtoNumber(8) val time: UInt,
|
@ProtoNumber(8) val time: UInt?,
|
||||||
@ProtoNumber(9) val original: UInt,
|
@ProtoNumber(9) val original: UInt?,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -217,7 +217,7 @@ data class IndexNode(
|
|||||||
@ProtoNumber(3) val storeId: UInt, // 0为旧服务器 1为nt服务器
|
@ProtoNumber(3) val storeId: UInt, // 0为旧服务器 1为nt服务器
|
||||||
@ProtoNumber(4) val uploadTime: ULong,
|
@ProtoNumber(4) val uploadTime: ULong,
|
||||||
@ProtoNumber(5) val ttl: ULong,
|
@ProtoNumber(5) val ttl: ULong,
|
||||||
@ProtoNumber(6) val subType: UInt,
|
@ProtoNumber(6) val subType: UInt? = null,
|
||||||
@ProtoNumber(7) val storeAppId: UInt? = null
|
@ProtoNumber(7) val storeAppId: UInt? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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
|
||||||
@ -9,7 +9,7 @@ import moe.fuqiuluo.symbols.Protobuf
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class NtV2RichMediaRsp(
|
data class NtV2RichMediaRsp(
|
||||||
@ProtoNumber(1) val head: RspHead,
|
@ProtoNumber(1) val head: RspHead?,
|
||||||
@ProtoNumber(2) val upload: UploadRsp?,
|
@ProtoNumber(2) val upload: UploadRsp?,
|
||||||
@ProtoNumber(3) val download: DownloadRsp?,
|
@ProtoNumber(3) val download: DownloadRsp?,
|
||||||
@ProtoNumber(4) val downloadRkeyRsp: DownloadRkeyRsp?,
|
@ProtoNumber(4) val downloadRkeyRsp: DownloadRkeyRsp?,
|
||||||
@ -26,8 +26,8 @@ class DownloadSafeRsp
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class UploadKeyRenewalRsp(
|
data class UploadKeyRenewalRsp(
|
||||||
@ProtoNumber(1) val ukey: String,
|
@ProtoNumber(1) val ukey: String?,
|
||||||
@ProtoNumber(2) val ukeyTtlSec: ULong,
|
@ProtoNumber(2) val ukeyTtlSec: ULong?,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -39,7 +39,7 @@ data class MsgInfoAuthRsp(
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class UploadCompletedRsp(
|
data class UploadCompletedRsp(
|
||||||
@ProtoNumber(1) val msgSeq: ULong
|
@ProtoNumber(1) val msgSeq: ULong?
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -47,13 +47,13 @@ class DeleteRsp
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class DownloadRkeyRsp(
|
data class DownloadRkeyRsp(
|
||||||
@ProtoNumber(1) val rkeys: List<RKeyInfo>
|
@ProtoNumber(1) val rkeys: List<RKeyInfo>?
|
||||||
)
|
)
|
||||||
|
|
||||||
@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?,
|
||||||
@ -61,8 +61,8 @@ data class RKeyInfo(
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class DownloadRsp(
|
data class DownloadRsp(
|
||||||
@ProtoNumber(1) val rkeyParam: String,
|
@ProtoNumber(1) val rkeyParam: String?,
|
||||||
@ProtoNumber(2) val rkeyTtlSec: ULong,
|
@ProtoNumber(2) val rkeyTtlSec: ULong?,
|
||||||
@ProtoNumber(3) val downloadInfo: DownloadInfo?,
|
@ProtoNumber(3) val downloadInfo: DownloadInfo?,
|
||||||
@ProtoNumber(4) val rkeyCreateTime: UInt?
|
@ProtoNumber(4) val rkeyCreateTime: UInt?
|
||||||
)
|
)
|
||||||
@ -80,16 +80,16 @@ data class DownloadInfo(
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class VideoExtInfo(
|
data class VideoExtInfo(
|
||||||
@ProtoNumber(1) val videoCodecFormat: UInt,
|
@ProtoNumber(1) val videoCodecFormat: UInt? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class UploadRsp(
|
data class UploadRsp(
|
||||||
@ProtoNumber(1) val ukey: String,
|
@ProtoNumber(1) val ukey: String?,
|
||||||
@ProtoNumber(2) val ukeyTtlSec: ULong,
|
@ProtoNumber(2) val ukeyTtlSec: ULong?,
|
||||||
@ProtoNumber(3) val ipv4: List<Ipv4>,
|
@ProtoNumber(3) val ipv4: List<Ipv4>?,
|
||||||
@ProtoNumber(4) val ipv6: List<Ipv6>,
|
@ProtoNumber(4) val ipv6: List<Ipv6>?,
|
||||||
@ProtoNumber(5) val msgSeq: ULong,
|
@ProtoNumber(5) val msgSeq: ULong?,
|
||||||
@ProtoNumber(6) val msgInfo: MsgInfo? = null,
|
@ProtoNumber(6) val msgInfo: MsgInfo? = null,
|
||||||
@ProtoNumber(7) val ext: List<RichmediaStorageTransInfo>? = null,
|
@ProtoNumber(7) val ext: List<RichmediaStorageTransInfo>? = null,
|
||||||
@ProtoNumber(8) val compatQMsg: ByteArray? = null,
|
@ProtoNumber(8) val compatQMsg: ByteArray? = null,
|
||||||
@ -98,11 +98,11 @@ data class UploadRsp(
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class SubFileInfo(
|
data class SubFileInfo(
|
||||||
@ProtoNumber(1) val subType: UInt,
|
@ProtoNumber(1) val subType: UInt?,
|
||||||
@ProtoNumber(2) val ukey: String,
|
@ProtoNumber(2) val ukey: String?,
|
||||||
@ProtoNumber(3) val ukeyTTLSec: ULong,
|
@ProtoNumber(3) val ukeyTTLSec: ULong?,
|
||||||
@ProtoNumber(4) val ipv4: List<Ipv4>,
|
@ProtoNumber(4) val ipv4: List<Ipv4>?,
|
||||||
@ProtoNumber(5) val ipv6: List<Ipv6>,
|
@ProtoNumber(5) val ipv6: List<Ipv6>?,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -132,8 +132,8 @@ data class Ipv6(
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class RspHead(
|
data class RspHead(
|
||||||
@ProtoNumber(1) val commonHead: CommonHead,
|
@ProtoNumber(1) val commonHead: CommonHead?,
|
||||||
@ProtoNumber(2) val retCode: UInt = 0u,
|
@ProtoNumber(2) val retCode: UInt = 0u,
|
||||||
@ProtoNumber(3) val msg: String
|
@ProtoNumber(3) val msg: String?
|
||||||
)
|
)
|
||||||
|
|
||||||
|
63
protobuf/src/main/java/protobuf/oidb/cmd0x388/Cmd0x388Req.kt
Normal file
63
protobuf/src/main/java/protobuf/oidb/cmd0x388/Cmd0x388Req.kt
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package protobuf.oidb.cmd0x388
|
||||||
|
|
||||||
|
import moe.fuqiuluo.symbols.EMPTY_BYTE_ARRAY
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.protobuf.ProtoNumber
|
||||||
|
import moe.fuqiuluo.symbols.Protobuf
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Cmd0x388ReqBody(
|
||||||
|
@ProtoNumber(1) var netType: Int = 0,
|
||||||
|
@ProtoNumber(2) var subCmd: Int = 0,
|
||||||
|
@ProtoNumber(3) var msgTryUpImg: ArrayList<TryUpImgReq>? = null,
|
||||||
|
// @ProtoNumber(4) var rpt_msg_getimg_url_req: ArrayList<GetImgUrlReq>? = null,
|
||||||
|
@ProtoNumber(5) var msgTryUpPttReq: ArrayList<TryUpPttReq>? = null,
|
||||||
|
// @ProtoNumber(6) var msgGetPttUrlReq: ArrayList<GetPttUrlReq>? = null,
|
||||||
|
@ProtoNumber(7) var commandId: Int = 0,
|
||||||
|
// @ProtoNumber(8) var rpt_msg_del_img_req: ArrayList<DelImgReq>? = null,
|
||||||
|
@ProtoNumber(1001) var extension: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
): Protobuf<Cmd0x388ReqBody>
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class TryUpImgReq(
|
||||||
|
@ProtoNumber(1) var groupCode: Long = 0,
|
||||||
|
@ProtoNumber(2) var srcUin: Long = 0,
|
||||||
|
@ProtoNumber(3) var fileId: Long? = null,
|
||||||
|
@ProtoNumber(4) var fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(5) var fileSize: Long = 0,
|
||||||
|
@ProtoNumber(6) var fileName: String = "",
|
||||||
|
@ProtoNumber(7) var srcTerm: Int = 0,
|
||||||
|
@ProtoNumber(8) var platformType: Int = 0,
|
||||||
|
@ProtoNumber(9) var buType: Int = 0,
|
||||||
|
@ProtoNumber(10) var picWidth: Int = 0,
|
||||||
|
@ProtoNumber(11) var picHeight: Int = 0,
|
||||||
|
@ProtoNumber(12) var picType: Int = 0,
|
||||||
|
@ProtoNumber(13) var buildVer: String = "",
|
||||||
|
@ProtoNumber(14) var innerIp: Int = 0,
|
||||||
|
@ProtoNumber(15) var appPicType: Int = 0,
|
||||||
|
@ProtoNumber(16) var originalPic: Int = 0,
|
||||||
|
@ProtoNumber(17) var fileIndex: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(18) var dstUin: Long = 0,
|
||||||
|
@ProtoNumber(19) var srvUpload: Int? = null,
|
||||||
|
@ProtoNumber(20) var transferUrl: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class TryUpPttReq(
|
||||||
|
@ProtoNumber(1) var groupCode: Long = 0,
|
||||||
|
@ProtoNumber(2) var srcUin: Long = 0,
|
||||||
|
@ProtoNumber(3) var fileId: Long = 0,
|
||||||
|
@ProtoNumber(4) var fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(5) var fileSize: Long = 0,
|
||||||
|
@ProtoNumber(6) var fileName: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(7) var srcTerm: Int = 0,
|
||||||
|
@ProtoNumber(8) var platformType: Int = 0,
|
||||||
|
@ProtoNumber(9) var buType: Int = 0,
|
||||||
|
@ProtoNumber(10) var buildVer: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(11) var innerIp: Int = 0,
|
||||||
|
@ProtoNumber(12) var voiceLength: Int = 0,
|
||||||
|
@ProtoNumber(13) var newUpChan: Boolean = false,
|
||||||
|
@ProtoNumber(14) var codec: Int = 0,
|
||||||
|
@ProtoNumber(15) var voiceType: Int = 0,
|
||||||
|
@ProtoNumber(16) var buId: Int = 0,
|
||||||
|
)
|
78
protobuf/src/main/java/protobuf/oidb/cmd0x388/Cmd0x388Rsp.kt
Normal file
78
protobuf/src/main/java/protobuf/oidb/cmd0x388/Cmd0x388Rsp.kt
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
@file:OptIn(ExperimentalSerializationApi::class)
|
||||||
|
|
||||||
|
package protobuf.oidb.cmd0x388
|
||||||
|
|
||||||
|
import moe.fuqiuluo.symbols.EMPTY_BYTE_ARRAY
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.protobuf.ProtoNumber
|
||||||
|
import moe.fuqiuluo.symbols.Protobuf
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Cmd0x388RspBody(
|
||||||
|
@ProtoNumber(1) var clientIp: UInt = 0u,
|
||||||
|
@ProtoNumber(2) var subCmd: UInt = 0u,
|
||||||
|
@ProtoNumber(3) var msgTryUpImgRsp: ArrayList<TryUpImgRsp>? = null,
|
||||||
|
@ProtoNumber(5) var msgTryUpPttRsp: ArrayList<TryUpPttRsp>? = null,
|
||||||
|
): Protobuf<Cmd0x388RspBody>
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class TryUpPttRsp(
|
||||||
|
@ProtoNumber(1) var fileId: Long = 0,
|
||||||
|
@ProtoNumber(2) var result: Int = 0,
|
||||||
|
@ProtoNumber(3) var failMsg: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(4) var fileExit: Boolean = false,
|
||||||
|
@ProtoNumber(5) var upIp: List<Int>? = null,
|
||||||
|
@ProtoNumber(6) var upPort: List<Int>? = null,
|
||||||
|
@ProtoNumber(7) var upUkey: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(8) var fileid: Long = 0,
|
||||||
|
@ProtoNumber(9) var upOffset: Long = 0,
|
||||||
|
@ProtoNumber(10) var blockSize: Long = 0,
|
||||||
|
@ProtoNumber(11) var fileKey: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(12) var channelType: Int = 0,
|
||||||
|
@ProtoNumber(26) var msgUpIp6: ArrayList<IPv6Info>? = null,
|
||||||
|
@ProtoNumber(27) var clientIp6: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
): Protobuf<TryUpPttRsp>
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class TryUpImgRsp(
|
||||||
|
@ProtoNumber(1) var extFileId: ULong = 0u, // 没有实际作用
|
||||||
|
@ProtoNumber(2) var result: UInt = 0u,
|
||||||
|
@ProtoNumber(3) var faiMsg: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(4) var fileExist: Boolean = false,
|
||||||
|
@ProtoNumber(5) var msgImgInfo: ImgInfo? = null, // 里面只是一堆垃圾
|
||||||
|
@ProtoNumber(6) var upIp: ArrayList<Long>? = null,
|
||||||
|
@ProtoNumber(7) var upPort: ArrayList<Int>? = null,
|
||||||
|
@ProtoNumber(8) var ukey: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(9) var fileId: Long = 0,
|
||||||
|
@ProtoNumber(10) var upOffset: ULong = 0u,
|
||||||
|
@ProtoNumber(11) var blockSize: Long = 0,
|
||||||
|
@ProtoNumber(12) var bool_new_big_chan: Boolean = false,
|
||||||
|
@ProtoNumber(26) var rpt_msg_up_ip6: ArrayList<IPv6Info>? = null,
|
||||||
|
@ProtoNumber(27) var client_ip6: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(1001) var msg_info4busi: TryUpInfo4Busi? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class TryUpInfo4Busi(
|
||||||
|
@ProtoNumber(1) var down_domain: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(2) var thumb_down_url: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(3) var original_down_url: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(4) var big_down_url: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(5) var file_resid: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class IPv6Info(
|
||||||
|
@ProtoNumber(1) var ip6: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(2) var port: UInt = 0u,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ImgInfo(
|
||||||
|
@ProtoNumber(1) var file_md5: ByteArray = EMPTY_BYTE_ARRAY,
|
||||||
|
@ProtoNumber(2) var file_type: UInt = 0u,
|
||||||
|
@ProtoNumber(3) var file_size: ULong = 0u,
|
||||||
|
@ProtoNumber(4) var file_width: UInt = 0u,
|
||||||
|
@ProtoNumber(5) var file_height: UInt = 0u,
|
||||||
|
)
|
@ -7,6 +7,8 @@ import kotlinx.serialization.Serializable
|
|||||||
import kotlinx.serialization.protobuf.ProtoNumber
|
import kotlinx.serialization.protobuf.ProtoNumber
|
||||||
import moe.fuqiuluo.symbols.Protobuf
|
import moe.fuqiuluo.symbols.Protobuf
|
||||||
|
|
||||||
|
const val DEFAULT_DEVICE_INFO = "i=&imsi=&mac=02:00:00:00:00:00&m=Shamrock&o=114514&a=1919810&sd=0&c64=1&sc=1&p=8000*8000&aid=123456789012345678901234567890abcdef&f=Tencent&mm=5610&cf=1726&cc=8&qimei=&qimei36=&sharpP=1&n=nether_world&support_xsj_live=false&client_mod=concise&timezone=America/La_Paz&material_sdk_version=&vh265=&refreshrate=10086&hwlevel=9&suphdr=1&is_teenager_mod=8&liveH265=&bmst=5&AV1=0"
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class QWebReq(
|
data class QWebReq(
|
||||||
@ProtoNumber(1) val seq: Int = 0,
|
@ProtoNumber(1) val seq: Int = 0,
|
||||||
|
@ -6,9 +6,13 @@ import com.tencent.mobileqq.app.BusinessObserver;
|
|||||||
import com.tencent.mobileqq.app.MessageHandler;
|
import com.tencent.mobileqq.app.MessageHandler;
|
||||||
import com.tencent.qphone.base.remote.ToServiceMsg;
|
import com.tencent.qphone.base.remote.ToServiceMsg;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import mqq.app.AppRuntime;
|
import mqq.app.AppRuntime;
|
||||||
|
|
||||||
public abstract class AppInterface extends AppRuntime {
|
public abstract class AppInterface extends AppRuntime {
|
||||||
|
private final ConcurrentHashMap<String, BusinessHandler> allHandler = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public String getCurrentNickname() {
|
public String getCurrentNickname() {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,10 @@ public abstract class BaseBusinessHandler extends OidbWrapper {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addBusinessObserver(ToServiceMsg toServiceMsg, BusinessObserver businessObserver, boolean z) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public final <T> T decodePacket(byte[] data, String name, T obj) {
|
public final <T> T decodePacket(byte[] data, String name, T obj) {
|
||||||
UniPacket uniPacket = new UniPacket(true);
|
UniPacket uniPacket = new UniPacket(true);
|
||||||
try {
|
try {
|
||||||
@ -24,6 +28,10 @@ public abstract class BaseBusinessHandler extends OidbWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean msgCmdFilter(String str) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract Set<String> getCommandList();
|
protected abstract Set<String> getCommandList();
|
||||||
|
|
||||||
protected abstract Set<String> getPushCommandList();
|
protected abstract Set<String> getPushCommandList();
|
||||||
|
@ -8,6 +8,8 @@ public abstract class BusinessHandler extends BaseBusinessHandler {
|
|||||||
public BusinessHandler(AppInterface appInterface) {
|
public BusinessHandler(AppInterface appInterface) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract Class<? extends BusinessObserver> observerClass();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<String> getCommandList() {
|
public Set<String> getCommandList() {
|
||||||
return null;
|
return null;
|
||||||
|
@ -0,0 +1,74 @@
|
|||||||
|
package com.tencent.mobileqq.data;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class MessageForPic extends MessageRecord {
|
||||||
|
public String SpeedInfo;
|
||||||
|
public String actMsgContentValue;
|
||||||
|
public String action;
|
||||||
|
public String bigMsgUrl;
|
||||||
|
public String bigThumbMsgUrl;
|
||||||
|
public int busiType;
|
||||||
|
public int fileSizeFlag;
|
||||||
|
public long groupFileID;
|
||||||
|
public long height;
|
||||||
|
public int imageType;
|
||||||
|
public boolean isInMixedMsg;
|
||||||
|
public boolean isMixed;
|
||||||
|
public boolean isRead;
|
||||||
|
public boolean isShareAppActionMsg;
|
||||||
|
public String localUUID;
|
||||||
|
public int mCurrlength;
|
||||||
|
public int mDownloadLength;
|
||||||
|
public long mPresendTransferedSize;
|
||||||
|
public int mShowLength;
|
||||||
|
public String md5;
|
||||||
|
//@RecordForTest
|
||||||
|
// public msg_ctrl$MsgCtrl msgCtrl;
|
||||||
|
public int msgVia;
|
||||||
|
public int ntChatType;
|
||||||
|
public String ntPeerUid;
|
||||||
|
public String path;
|
||||||
|
//public PicMessageExtraData picExtraData;
|
||||||
|
public int picExtraFlag;
|
||||||
|
public Object picExtraObject;
|
||||||
|
public int previewed;
|
||||||
|
public String rawMsgUrl;
|
||||||
|
/// public ReportInfo reportInfo;
|
||||||
|
//public MsgRecordParams rootMsgRecordParams;
|
||||||
|
public String serverStoreSource;
|
||||||
|
public long shareAppID;
|
||||||
|
public long size;
|
||||||
|
public long subTypeId;
|
||||||
|
public int thumbHeight;
|
||||||
|
public String thumbMsgUrl;
|
||||||
|
public int thumbWidth;
|
||||||
|
//public ThumbWidthHeightDP thumbWidthHeightDP;
|
||||||
|
public int type;
|
||||||
|
public String uuid;
|
||||||
|
public long width;
|
||||||
|
public boolean isDownStatusReady = false;
|
||||||
|
public int subMsgId = 0;
|
||||||
|
public int isReport = 0;
|
||||||
|
public int subVersion = 5;
|
||||||
|
public int preDownState = -1;
|
||||||
|
public int preDownNetworkType = -1;
|
||||||
|
public long DSKey = 0;
|
||||||
|
public int mNotPredownloadReason = 0;
|
||||||
|
public int subThumbWidth = -1;
|
||||||
|
public int subThumbHeight = -1;
|
||||||
|
public int aiofileType = -1;
|
||||||
|
public int subMsgType = -1;
|
||||||
|
public boolean bEnableEnc = false;
|
||||||
|
public int thumbSize = -1;
|
||||||
|
public boolean isBlessPic = false;
|
||||||
|
public boolean sync2Story = false;
|
||||||
|
public boolean isQzonePic = false;
|
||||||
|
public boolean isStoryPhoto = false;
|
||||||
|
public long replyRealSourceMsgId = -1;
|
||||||
|
|
||||||
|
public String toLogString() {
|
||||||
|
return "path:" + this.path + ",uuid:" + this.uuid + ",md5:" + this.md5 + ",size:" + this.size + ",groupFileID:" + this.groupFileID;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.tencent.mobileqq.msf.sdk;
|
||||||
|
|
||||||
|
import com.tencent.qphone.base.remote.FromServiceMsg;
|
||||||
|
import com.tencent.qphone.base.remote.ToServiceMsg;
|
||||||
|
|
||||||
|
public class MsfMessagePair {
|
||||||
|
public FromServiceMsg fromServiceMsg;
|
||||||
|
public String sendProcess;
|
||||||
|
public ToServiceMsg toServiceMsg;
|
||||||
|
|
||||||
|
public MsfMessagePair(String str, ToServiceMsg toServiceMsg, FromServiceMsg fromServiceMsg) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public MsfMessagePair(ToServiceMsg toServiceMsg, FromServiceMsg fromServiceMsg) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
package com.tencent.mobileqq.transfile;
|
package com.tencent.mobileqq.transfile;
|
||||||
|
|
||||||
public class BaseTransProcessor {
|
import com.tencent.mobileqq.utils.httputils.IHttpCommunicatorListener;
|
||||||
|
|
||||||
|
public class BaseTransProcessor implements IHttpCommunicatorListener {
|
||||||
public FileMsg file;
|
public FileMsg file;
|
||||||
|
|
||||||
public long getFileStatus() {
|
public long getFileStatus() {
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
package com.tencent.mobileqq.transfile;
|
||||||
|
|
||||||
|
public class BaseUploadProcessor extends BaseTransProcessor {
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,9 @@
|
|||||||
package com.tencent.mobileqq.transfile;
|
package com.tencent.mobileqq.transfile;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
public class FileMsg {
|
public class FileMsg {
|
||||||
public static final int STATUS_FILE_EXPIRED = 5002;
|
public static final int STATUS_FILE_EXPIRED = 5002;
|
||||||
public static final int STATUS_FILE_TRANSFERING = 5000;
|
public static final int STATUS_FILE_TRANSFERING = 5000;
|
||||||
@ -107,4 +111,58 @@ public class FileMsg {
|
|||||||
public static final int UIN_BUDDY = 0;
|
public static final int UIN_BUDDY = 0;
|
||||||
public static final int UIN_DISCUSS = 2;
|
public static final int UIN_DISCUSS = 2;
|
||||||
public static final int UIN_TROOP = 1;
|
public static final int UIN_TROOP = 1;
|
||||||
|
|
||||||
|
public String domain;
|
||||||
|
public String downDomain;
|
||||||
|
public long endTime;
|
||||||
|
public int errorCode;
|
||||||
|
public String errorMessage;
|
||||||
|
public File file;
|
||||||
|
public long fileID;
|
||||||
|
public String fileKey;
|
||||||
|
public String fileMd5;
|
||||||
|
public String filePath;
|
||||||
|
public long fileSize;
|
||||||
|
public int fileType;
|
||||||
|
public String fileUrl;
|
||||||
|
public String forwardTaskKey;
|
||||||
|
public String friendUin;
|
||||||
|
public int isRead;
|
||||||
|
public boolean isStreamMode;
|
||||||
|
public int lastStatus;
|
||||||
|
public byte[] localFileMd5;
|
||||||
|
public String logTag;
|
||||||
|
public long mSecMsgId;
|
||||||
|
public long mSubMsgId;
|
||||||
|
public String mUin;
|
||||||
|
public String msgImageData;
|
||||||
|
public String msgTime;
|
||||||
|
public String orgiDownUrl;
|
||||||
|
public String peerUin;
|
||||||
|
public int picScale;
|
||||||
|
public long picThumbSize;
|
||||||
|
public BaseTransProcessor processor;
|
||||||
|
public boolean processorDoReportSelf;
|
||||||
|
public int pttSlicePos;
|
||||||
|
public String pttSliceText;
|
||||||
|
public OutputStream revStream;
|
||||||
|
public String selfUin;
|
||||||
|
public InputStream sendStream;
|
||||||
|
public String serverPath;
|
||||||
|
public long startTime;
|
||||||
|
public int status;
|
||||||
|
public long stepUrlCost;
|
||||||
|
public String suffixType;
|
||||||
|
public String thumbDownUrl;
|
||||||
|
public String thumbPath;
|
||||||
|
public String thumbUrl;
|
||||||
|
public String tmpFilePath;
|
||||||
|
public byte[] transferData;
|
||||||
|
public long transferedSize;
|
||||||
|
public String uKey;
|
||||||
|
public int uinType;
|
||||||
|
public long uniseq;
|
||||||
|
public String[] urls;
|
||||||
|
public byte[] userInfo;
|
||||||
|
public String uuidPath;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
package com.tencent.mobileqq.transfile;
|
package com.tencent.mobileqq.transfile;
|
||||||
|
|
||||||
public class GroupPicUploadProcessor {
|
import com.tencent.mobileqq.utils.httputils.IHttpCommunicatorListener;
|
||||||
|
|
||||||
|
public class GroupPicUploadProcessor extends BaseUploadProcessor {
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,11 @@ public class MsgService {
|
|||||||
public void addMsgListener(IKernelMsgListener listener) {
|
public void addMsgListener(IKernelMsgListener listener) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeMsgListener(@NotNull IKernelMsgListener iKernelMsgListener) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public String getRichMediaFilePathForGuild(@NotNull RichMediaFilePathInfo richMediaFilePathInfo) {
|
public String getRichMediaFilePathForGuild(@NotNull RichMediaFilePathInfo richMediaFilePathInfo) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
public interface IKernelMsgService {
|
public interface IKernelMsgService {
|
||||||
void deleteMsg(Contact contact, ArrayList<Long> msgIdList, IOperateCallback callback);
|
void deleteMsg(Contact contact, ArrayList<Long> msgIdList, IOperateCallback cb);
|
||||||
|
|
||||||
void fetchLongMsg(Contact contact, long msgId);
|
void fetchLongMsg(Contact contact, long msgId);
|
||||||
|
|
||||||
|
@ -2,17 +2,20 @@ package com.tencent.qqnt.kernel.nativeinterface;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public final class InlineKeyboardButton {
|
||||||
public final class InlineKeyboardButton {
|
int anchor;
|
||||||
boolean atBotShowChannelList;
|
boolean atBotShowChannelList;
|
||||||
int clickLimit;
|
int clickLimit;
|
||||||
String data;
|
String data;
|
||||||
|
boolean enter;
|
||||||
String id;
|
String id;
|
||||||
|
boolean isReply;
|
||||||
String label;
|
String label;
|
||||||
int permissionType;
|
int permissionType;
|
||||||
ArrayList<String> specifyRoleIds;
|
ArrayList<String> specifyRoleIds;
|
||||||
ArrayList<String> specifyTinyids;
|
ArrayList<String> specifyTinyids;
|
||||||
int style;
|
int style;
|
||||||
|
ArrayList<SubscribeMsgTemplateID> subscribeDataTemplateIds;
|
||||||
int type;
|
int type;
|
||||||
String unsupportTips;
|
String unsupportTips;
|
||||||
String visitedLabel;
|
String visitedLabel;
|
||||||
@ -25,6 +28,11 @@ public final class InlineKeyboardButton {
|
|||||||
this.data = "";
|
this.data = "";
|
||||||
this.specifyRoleIds = new ArrayList<>();
|
this.specifyRoleIds = new ArrayList<>();
|
||||||
this.specifyTinyids = new ArrayList<>();
|
this.specifyTinyids = new ArrayList<>();
|
||||||
|
this.subscribeDataTemplateIds = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAnchor() {
|
||||||
|
return this.anchor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getAtBotShowChannelList() {
|
public boolean getAtBotShowChannelList() {
|
||||||
@ -39,10 +47,18 @@ public final class InlineKeyboardButton {
|
|||||||
return this.data;
|
return this.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getEnter() {
|
||||||
|
return this.enter;
|
||||||
|
}
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return this.id;
|
return this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getIsReply() {
|
||||||
|
return this.isReply;
|
||||||
|
}
|
||||||
|
|
||||||
public String getLabel() {
|
public String getLabel() {
|
||||||
return this.label;
|
return this.label;
|
||||||
}
|
}
|
||||||
@ -63,6 +79,10 @@ public final class InlineKeyboardButton {
|
|||||||
return this.style;
|
return this.style;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ArrayList<SubscribeMsgTemplateID> getSubscribeDataTemplateIds() {
|
||||||
|
return this.subscribeDataTemplateIds;
|
||||||
|
}
|
||||||
|
|
||||||
public int getType() {
|
public int getType() {
|
||||||
return this.type;
|
return this.type;
|
||||||
}
|
}
|
||||||
@ -76,14 +96,14 @@ public final class InlineKeyboardButton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "InlineKeyboardButton{id=" + this.id + ",label=" + this.label + ",visitedLabel=" + this.visitedLabel + ",style=" + this.style + ",type=" + this.type + ",clickLimit=" + this.clickLimit + ",unsupportTips=" + this.unsupportTips + ",data=" + this.data + ",atBotShowChannelList=" + this.atBotShowChannelList + ",permissionType=" + this.permissionType + ",specifyRoleIds=" + this.specifyRoleIds + ",specifyTinyids=" + this.specifyTinyids + ",}";
|
return "InlineKeyboardButton{id=" + this.id + ",label=" + this.label + ",visitedLabel=" + this.visitedLabel + ",style=" + this.style + ",type=" + this.type + ",clickLimit=" + this.clickLimit + ",unsupportTips=" + this.unsupportTips + ",data=" + this.data + ",atBotShowChannelList=" + this.atBotShowChannelList + ",permissionType=" + this.permissionType + ",specifyRoleIds=" + this.specifyRoleIds + ",specifyTinyids=" + this.specifyTinyids + ",isReply=" + this.isReply + ",anchor=" + this.anchor + ",enter=" + this.enter + ",subscribeDataTemplateIds=" + this.subscribeDataTemplateIds + ",}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public InlineKeyboardButton(String str, String str2, String str3, int i, int i2, int i3, String str4, String str5, boolean z, int i4, ArrayList<String> arrayList, ArrayList<String> arrayList2, boolean z2, int i5, boolean z3, ArrayList<SubscribeMsgTemplateID> arrayList3) {
|
public InlineKeyboardButton(String str, String str2, String str3, int i, int i2, int i3, String str4, String str5, boolean z, int i4, ArrayList<String> arrayList, ArrayList<String> arrayList2) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public InlineKeyboardButton(String str, String str2, String str3, int i2, int i3, int i4, String str4, String str5, boolean z, int i5, ArrayList<String> arrayList, ArrayList<String> arrayList2) {
|
public InlineKeyboardButton(String str, String str2, String str3, int i2, int i3, int i4, String str4, String str5, boolean z, int i5, ArrayList<String> arrayList, ArrayList<String> arrayList2, boolean z2, int i6, boolean z3, ArrayList<SubscribeMsgTemplateID> arrayList3) {
|
||||||
this.id = "";
|
this.id = "";
|
||||||
this.label = "";
|
this.label = "";
|
||||||
this.visitedLabel = "";
|
this.visitedLabel = "";
|
||||||
@ -91,6 +111,7 @@ public final class InlineKeyboardButton {
|
|||||||
this.data = "";
|
this.data = "";
|
||||||
this.specifyRoleIds = new ArrayList<>();
|
this.specifyRoleIds = new ArrayList<>();
|
||||||
this.specifyTinyids = new ArrayList<>();
|
this.specifyTinyids = new ArrayList<>();
|
||||||
|
this.subscribeDataTemplateIds = new ArrayList<>();
|
||||||
this.id = str;
|
this.id = str;
|
||||||
this.label = str2;
|
this.label = str2;
|
||||||
this.visitedLabel = str3;
|
this.visitedLabel = str3;
|
||||||
@ -103,5 +124,9 @@ public final class InlineKeyboardButton {
|
|||||||
this.permissionType = i5;
|
this.permissionType = i5;
|
||||||
this.specifyRoleIds = arrayList;
|
this.specifyRoleIds = arrayList;
|
||||||
this.specifyTinyids = arrayList2;
|
this.specifyTinyids = arrayList2;
|
||||||
|
this.isReply = z2;
|
||||||
|
this.anchor = i6;
|
||||||
|
this.enter = z3;
|
||||||
|
this.subscribeDataTemplateIds = arrayList3;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -39,6 +39,7 @@ public class MsgRecord {
|
|||||||
boolean isOnlineMsg;
|
boolean isOnlineMsg;
|
||||||
FromRoleInfo levelRoleInfo;
|
FromRoleInfo levelRoleInfo;
|
||||||
HashMap<Integer, MsgAttributeInfo> msgAttrs;
|
HashMap<Integer, MsgAttributeInfo> msgAttrs;
|
||||||
|
byte[] msgEventInfo;
|
||||||
long msgId;
|
long msgId;
|
||||||
byte[] msgMeta;
|
byte[] msgMeta;
|
||||||
long msgRandom;
|
long msgRandom;
|
||||||
@ -231,6 +232,10 @@ public class MsgRecord {
|
|||||||
return this.msgAttrs;
|
return this.msgAttrs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] getMsgEventInfo() {
|
||||||
|
return this.msgEventInfo;
|
||||||
|
}
|
||||||
|
|
||||||
public long getMsgId() {
|
public long getMsgId() {
|
||||||
return this.msgId;
|
return this.msgId;
|
||||||
}
|
}
|
||||||
@ -332,10 +337,10 @@ public class MsgRecord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "MsgRecord{msgId=" + this.msgId + ",msgRandom=" + this.msgRandom + ",msgSeq=" + this.msgSeq + ",cntSeq=" + this.cntSeq + ",chatType=" + this.chatType + ",msgType=" + this.msgType + ",subMsgType=" + this.subMsgType + ",sendType=" + this.sendType + ",senderUid=" + this.senderUid + ",peerUid=" + this.peerUid + ",channelId=" + this.channelId + ",guildId=" + this.guildId + ",guildCode=" + this.guildCode + ",fromUid=" + this.fromUid + ",fromAppid=" + this.fromAppid + ",msgTime=" + this.msgTime + ",msgMeta=" + this.msgMeta + ",sendStatus=" + this.sendStatus + ",sendRemarkName=" + this.sendRemarkName + ",sendMemberName=" + this.sendMemberName + ",sendNickName=" + this.sendNickName + ",guildName=" + this.guildName + ",channelName=" + this.channelName + ",elements=" + this.elements + ",records=" + this.records + ",emojiLikesList=" + this.emojiLikesList + ",commentCnt=" + this.commentCnt + ",directMsgFlag=" + this.directMsgFlag + ",directMsgMembers=" + this.directMsgMembers + ",peerName=" + this.peerName + ",freqLimitInfo=" + this.freqLimitInfo + ",editable=" + this.editable + ",avatarMeta=" + this.avatarMeta + ",avatarPendant=" + this.avatarPendant + ",feedId=" + this.feedId + ",roleId=" + this.roleId + ",timeStamp=" + this.timeStamp + ",clientIdentityInfo=" + this.clientIdentityInfo + ",isImportMsg=" + this.isImportMsg + ",atType=" + this.atType + ",roleType=" + this.roleType + ",fromChannelRoleInfo=" + this.fromChannelRoleInfo + ",fromGuildRoleInfo=" + this.fromGuildRoleInfo + ",levelRoleInfo=" + this.levelRoleInfo + ",recallTime=" + this.recallTime + ",isOnlineMsg=" + this.isOnlineMsg + ",generalFlags=" + this.generalFlags + ",clientSeq=" + this.clientSeq + ",fileGroupSize=" + this.fileGroupSize + ",foldingInfo=" + this.foldingInfo + ",multiTransInfo=" + this.multiTransInfo + ",senderUin=" + this.senderUin + ",peerUin=" + this.peerUin + ",msgAttrs=" + this.msgAttrs + ",anonymousExtInfo=" + this.anonymousExtInfo + ",nameType=" + this.nameType + ",avatarFlag=" + this.avatarFlag + ",extInfoForUI=" + this.extInfoForUI + ",personalMedal=" + this.personalMedal + ",categoryManage=" + this.categoryManage + ",}";
|
return "MsgRecord{msgId=" + this.msgId + ",msgRandom=" + this.msgRandom + ",msgSeq=" + this.msgSeq + ",cntSeq=" + this.cntSeq + ",chatType=" + this.chatType + ",msgType=" + this.msgType + ",subMsgType=" + this.subMsgType + ",sendType=" + this.sendType + ",senderUid=" + this.senderUid + ",peerUid=" + this.peerUid + ",channelId=" + this.channelId + ",guildId=" + this.guildId + ",guildCode=" + this.guildCode + ",fromUid=" + this.fromUid + ",fromAppid=" + this.fromAppid + ",msgTime=" + this.msgTime + ",msgMeta=" + this.msgMeta + ",sendStatus=" + this.sendStatus + ",sendRemarkName=" + this.sendRemarkName + ",sendMemberName=" + this.sendMemberName + ",sendNickName=" + this.sendNickName + ",guildName=" + this.guildName + ",channelName=" + this.channelName + ",elements=" + this.elements + ",records=" + this.records + ",emojiLikesList=" + this.emojiLikesList + ",commentCnt=" + this.commentCnt + ",directMsgFlag=" + this.directMsgFlag + ",directMsgMembers=" + this.directMsgMembers + ",peerName=" + this.peerName + ",freqLimitInfo=" + this.freqLimitInfo + ",editable=" + this.editable + ",avatarMeta=" + this.avatarMeta + ",avatarPendant=" + this.avatarPendant + ",feedId=" + this.feedId + ",roleId=" + this.roleId + ",timeStamp=" + this.timeStamp + ",clientIdentityInfo=" + this.clientIdentityInfo + ",isImportMsg=" + this.isImportMsg + ",atType=" + this.atType + ",roleType=" + this.roleType + ",fromChannelRoleInfo=" + this.fromChannelRoleInfo + ",fromGuildRoleInfo=" + this.fromGuildRoleInfo + ",levelRoleInfo=" + this.levelRoleInfo + ",recallTime=" + this.recallTime + ",isOnlineMsg=" + this.isOnlineMsg + ",generalFlags=" + this.generalFlags + ",clientSeq=" + this.clientSeq + ",fileGroupSize=" + this.fileGroupSize + ",foldingInfo=" + this.foldingInfo + ",multiTransInfo=" + this.multiTransInfo + ",senderUin=" + this.senderUin + ",peerUin=" + this.peerUin + ",msgAttrs=" + this.msgAttrs + ",anonymousExtInfo=" + this.anonymousExtInfo + ",nameType=" + this.nameType + ",avatarFlag=" + this.avatarFlag + ",extInfoForUI=" + this.extInfoForUI + ",personalMedal=" + this.personalMedal + ",categoryManage=" + this.categoryManage + ",msgEventInfo=" + this.msgEventInfo + ",}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public MsgRecord(long j2, long j3, long j4, long j5, int i2, int i3, int i4, int i5, String str, String str2, String str3, String str4, long j6, long j7, long j8, long j9, byte[] bArr, int i6, String str5, String str6, String str7, String str8, String str9, ArrayList<MsgElement> arrayList, ArrayList<MsgRecord> arrayList2, ArrayList<MsgEmojiLikes> arrayList3, long j10, int i7, ArrayList<DirectMsgMember> arrayList4, String str10, FreqLimitInfo freqLimitInfo, boolean z, String str11, String str12, String str13, long j11, long j12, GProClientIdentity gProClientIdentity, boolean z2, int i8, int i9, FromRoleInfo fromRoleInfo, FromRoleInfo fromRoleInfo2, FromRoleInfo fromRoleInfo3, long j13, boolean z3, byte[] bArr2, long j14, Integer num, FoldingInfo foldingInfo, MultiTransInfo multiTransInfo, long j15, long j16, HashMap<Integer, MsgAttributeInfo> hashMap, AnonymousExtInfo anonymousExtInfo, int i10, int i11, byte[] bArr3, GProMedal gProMedal, int i12) {
|
public MsgRecord(long j2, long j3, long j4, long j5, int i2, int i3, int i4, int i5, String str, String str2, String str3, String str4, long j6, long j7, long j8, long j9, byte[] bArr, int i6, String str5, String str6, String str7, String str8, String str9, ArrayList<MsgElement> arrayList, ArrayList<MsgRecord> arrayList2, ArrayList<MsgEmojiLikes> arrayList3, long j10, int i7, ArrayList<DirectMsgMember> arrayList4, String str10, FreqLimitInfo freqLimitInfo, boolean z, String str11, String str12, String str13, long j11, long j12, GProClientIdentity gProClientIdentity, boolean z2, int i8, int i9, FromRoleInfo fromRoleInfo, FromRoleInfo fromRoleInfo2, FromRoleInfo fromRoleInfo3, long j13, boolean z3, byte[] bArr2, long j14, Integer num, FoldingInfo foldingInfo, MultiTransInfo multiTransInfo, long j15, long j16, HashMap<Integer, MsgAttributeInfo> hashMap, AnonymousExtInfo anonymousExtInfo, int i10, int i11, byte[] bArr3, GProMedal gProMedal, int i12, byte[] bArr4) {
|
||||||
this.senderUid = "";
|
this.senderUid = "";
|
||||||
this.peerUid = "";
|
this.peerUid = "";
|
||||||
this.channelId = "";
|
this.channelId = "";
|
||||||
@ -419,5 +424,6 @@ public class MsgRecord {
|
|||||||
this.extInfoForUI = bArr3;
|
this.extInfoForUI = bArr3;
|
||||||
this.personalMedal = gProMedal;
|
this.personalMedal = gProMedal;
|
||||||
this.categoryManage = i12;
|
this.categoryManage = i12;
|
||||||
|
this.msgEventInfo = bArr4;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -208,6 +208,8 @@ public final class PicElement implements IKernelModel {
|
|||||||
this.isFlashPic = bool;
|
this.isFlashPic = bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setStoreID(int i2) {
|
||||||
|
}
|
||||||
public void setMd5HexStr(String str) {
|
public void setMd5HexStr(String str) {
|
||||||
this.md5HexStr = str;
|
this.md5HexStr = str;
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,13 @@ public abstract class AppRuntime {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MobileQQ getApplication() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startServlet(NewIntent newIntent) {
|
||||||
|
}
|
||||||
|
|
||||||
public <T extends IRuntimeService> T getRuntimeService(Class<T> cls, String namespace) {
|
public <T extends IRuntimeService> T getRuntimeService(Class<T> cls, String namespace) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
29
qqinterface/src/main/java/mqq/app/NewIntent.java
Normal file
29
qqinterface/src/main/java/mqq/app/NewIntent.java
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package mqq.app;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
import com.tencent.mobileqq.app.BusinessObserver;
|
||||||
|
|
||||||
|
public class NewIntent extends Intent {
|
||||||
|
public boolean runNow;
|
||||||
|
|
||||||
|
public NewIntent(Context context, Class<? extends Servlet> cls) {
|
||||||
|
super(context, cls);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BusinessObserver getObserver() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isWithouLogin() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setObserver(BusinessObserver businessObserver) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWithouLogin(boolean z) {
|
||||||
|
}
|
||||||
|
}
|
@ -26,7 +26,7 @@ buildscript {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("com.android.tools:r8:8.2.47")
|
classpath("com.android.tools:r8:8.3.37")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,8 +34,9 @@ rootProject.name = "Shamrock"
|
|||||||
include(
|
include(
|
||||||
":app",
|
":app",
|
||||||
":xposed",
|
":xposed",
|
||||||
":qqinterface"
|
":qqinterface",
|
||||||
)
|
":protobuf",
|
||||||
include(":protobuf")
|
":processor",
|
||||||
include(":processor")
|
":annotations",
|
||||||
include(":annotations")
|
":kritor"
|
||||||
|
)
|
@ -1,9 +1,11 @@
|
|||||||
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.library")
|
id("com.android.library")
|
||||||
id("org.jetbrains.kotlin.android")
|
id("org.jetbrains.kotlin.android")
|
||||||
id("kotlin-kapt")
|
id("kotlin-kapt")
|
||||||
id("com.google.devtools.ksp") version "1.9.21-1.0.15"
|
id("com.google.devtools.ksp") version "1.9.22-1.0.17"
|
||||||
kotlin("plugin.serialization") version "1.9.21"
|
kotlin("plugin.serialization") version "1.9.22"
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@ -58,9 +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"))
|
||||||
|
|
||||||
|
implementation(project(":kritor"))
|
||||||
implementation(project(":protobuf"))
|
implementation(project(":protobuf"))
|
||||||
implementation(project(":annotations"))
|
implementation(project(":annotations"))
|
||||||
ksp(project(":processor"))
|
ksp(project(":processor"))
|
||||||
@ -70,27 +73,20 @@ dependencies {
|
|||||||
DEPENDENCY_ANDROIDX.forEach {
|
DEPENDENCY_ANDROIDX.forEach {
|
||||||
implementation(it)
|
implementation(it)
|
||||||
}
|
}
|
||||||
implementation(DEPENDENCY_JAVA_WEBSOCKET)
|
|
||||||
implementation(DEPENDENCY_PROTOBUF)
|
|
||||||
implementation(DEPENDENCY_JSON5K)
|
|
||||||
|
|
||||||
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("server", "core"))
|
|
||||||
implementation(ktor("server", "host-common"))
|
|
||||||
implementation(ktor("server", "status-pages"))
|
|
||||||
implementation(ktor("server", "netty"))
|
|
||||||
implementation(ktor("server", "content-negotiation"))
|
|
||||||
implementation(ktor("client", "core"))
|
implementation(ktor("client", "core"))
|
||||||
implementation(ktor("client", "content-negotiation"))
|
implementation(ktor("client", "okhttp"))
|
||||||
implementation(ktor("client", "cio"))
|
|
||||||
implementation(ktor("serialization", "kotlinx-json"))
|
implementation(ktor("serialization", "kotlinx-json"))
|
||||||
implementation(ktor("network", "tls-certificates"))
|
|
||||||
|
implementation(grpc("protobuf", "1.62.2"))
|
||||||
|
implementation(grpc("kotlin-stub", "1.4.1"))
|
||||||
|
implementation(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")
|
||||||
@ -99,4 +95,8 @@ dependencies {
|
|||||||
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
180
xposed/proguard-rules.pro
vendored
180
xposed/proguard-rules.pro
vendored
@ -18,182 +18,4 @@
|
|||||||
|
|
||||||
# If you keep the line number information, uncomment this to
|
# If you keep the line number information, uncomment this to
|
||||||
# hide the original source file name.
|
# hide the original source file name.
|
||||||
#-renamesourcefileattribute SourceFile
|
#-renamesourcefileattribute SourceFile
|
||||||
|
|
||||||
|
|
||||||
# Never inline methods, but allow shrinking and obfuscation.
|
|
||||||
-keepclassmembernames,allowobfuscation,allowshrinking class androidx.core.view.ViewCompat$Api* {
|
|
||||||
<methods>;
|
|
||||||
}
|
|
||||||
-keepclassmembernames,allowobfuscation,allowshrinking class androidx.core.view.WindowInsetsCompat$*Impl* {
|
|
||||||
<methods>;
|
|
||||||
}
|
|
||||||
-keepclassmembernames,allowobfuscation,allowshrinking class androidx.core.app.NotificationCompat$*$Api*Impl {
|
|
||||||
<methods>;
|
|
||||||
}
|
|
||||||
-keepclassmembernames,allowobfuscation,allowshrinking class androidx.core.os.UserHandleCompat$Api*Impl {
|
|
||||||
<methods>;
|
|
||||||
}
|
|
||||||
-keepclassmembernames,allowobfuscation,allowshrinking class androidx.core.widget.EdgeEffectCompat$Api*Impl {
|
|
||||||
<methods>;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Keep metadata about inner emulated classes. This helps with
|
|
||||||
# reflection on older platforms, but can be omitted if the
|
|
||||||
# metadata usage is not present in the app.
|
|
||||||
|
|
||||||
-keepclassmembers class * {
|
|
||||||
** CREATOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Keep the special static methods that are required in all enumeration
|
|
||||||
# classes.
|
|
||||||
-keepclassmembers enum * {
|
|
||||||
public static **[] values();
|
|
||||||
public static ** valueOf(java.lang.String);
|
|
||||||
}
|
|
||||||
|
|
||||||
-keep public class androidx.appcompat.widget.** { *; }
|
|
||||||
-keep public class androidx.appcompat.app.** { *; }
|
|
||||||
-keep public class androidx.appcompat.view.menu.** { *; }
|
|
||||||
|
|
||||||
# Ensure that reflectively-loaded inflater is not obfuscated. This can be
|
|
||||||
# removed when we stop supporting AAPT1 builds.
|
|
||||||
-keepnames class androidx.appcompat.app.AppCompatViewInflater
|
|
||||||
# aapt is not able to read app::actionViewClass and app:actionProviderClass to produce proguard
|
|
||||||
# keep rules. Add a commonly used SearchView to the keep list until b/109831488 is resolved.
|
|
||||||
-keep class androidx.appcompat.widget.SearchView { <init>(...); }
|
|
||||||
|
|
||||||
# CoordinatorLayout resolves the behaviors of its child components with reflection.
|
|
||||||
-keep public class * extends androidx.coordinatorlayout.widget.CoordinatorLayout$Behavior {
|
|
||||||
public <init>(android.content.Context, android.util.AttributeSet);
|
|
||||||
public <init>();
|
|
||||||
}
|
|
||||||
|
|
||||||
# Make sure we keep annotations for CoordinatorLayout's DefaultBehavior
|
|
||||||
-keepattributes RuntimeVisible*Annotation*
|
|
||||||
|
|
||||||
-if class androidx.appcompat.app.AppCompatViewInflater
|
|
||||||
-keep class com.google.android.material.theme.MaterialComponentsViewInflater {
|
|
||||||
<init>();
|
|
||||||
}
|
|
||||||
|
|
||||||
# Keep `Companion` object fields of serializable classes.
|
|
||||||
# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
|
|
||||||
-if @kotlinx.serialization.Serializable class **
|
|
||||||
-keepclassmembers class <1> {
|
|
||||||
static <1>$Companion Companion;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Keep `serializer()` on companion objects (both default and named) of serializable classes.
|
|
||||||
-if @kotlinx.serialization.Serializable class ** {
|
|
||||||
static **$* *;
|
|
||||||
}
|
|
||||||
-keepclassmembers class <2>$<3> {
|
|
||||||
kotlinx.serialization.KSerializer serializer(...);
|
|
||||||
}
|
|
||||||
|
|
||||||
# Keep `INSTANCE.serializer()` of serializable objects.
|
|
||||||
-if @kotlinx.serialization.Serializable class ** {
|
|
||||||
public static ** INSTANCE;
|
|
||||||
}
|
|
||||||
-keepclassmembers class <1> {
|
|
||||||
public static <1> INSTANCE;
|
|
||||||
kotlinx.serialization.KSerializer serializer(...);
|
|
||||||
}
|
|
||||||
|
|
||||||
# @Serializable and @Polymorphic are used at runtime for polymorphic serialization.
|
|
||||||
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
|
|
||||||
|
|
||||||
# Don't print notes about potential mistakes or omissions in the configuration for kotlinx-serialization classes
|
|
||||||
# See also https://github.com/Kotlin/kotlinx.serialization/issues/1900
|
|
||||||
-dontnote kotlinx.serialization.**
|
|
||||||
|
|
||||||
# Serialization core uses `java.lang.ClassValue` for caching inside these specified classes.
|
|
||||||
# If there is no `java.lang.ClassValue` (for example, in Android), then R8/ProGuard will print a warning.
|
|
||||||
# However, since in this case they will not be used, we can disable these warnings
|
|
||||||
-dontwarn kotlinx.serialization.internal.ClassValueReferences
|
|
||||||
|
|
||||||
# Rule to save runtime annotations on serializable class.
|
|
||||||
# If the R8 full mode is used, annotations are removed from classes-files.
|
|
||||||
#
|
|
||||||
# For the annotation serializer, it is necessary to read the `Serializable` annotation inside the serializer<T>() function - if it is present,
|
|
||||||
# then `SealedClassSerializer` is used, if absent, then `PolymorphicSerializer'.
|
|
||||||
#
|
|
||||||
# When using R8 full mode, all interfaces will be serialized using `PolymorphicSerializer`.
|
|
||||||
#
|
|
||||||
# see https://github.com/Kotlin/kotlinx.serialization/issues/2050
|
|
||||||
|
|
||||||
-if @kotlinx.serialization.Serializable class **
|
|
||||||
-keep, allowshrinking, allowoptimization, allowobfuscation, allowaccessmodification class <1>
|
|
||||||
|
|
||||||
# Entry point for retaining MainDispatcherLoader which uses a ServiceLoader.
|
|
||||||
-keep class kotlinx.coroutines.Dispatchers {
|
|
||||||
** getMain();
|
|
||||||
}
|
|
||||||
|
|
||||||
# Entry point for retaining CoroutineExceptionHandlerImpl.handlers which uses a ServiceLoader.
|
|
||||||
-keep class kotlinx.coroutines.CoroutineExceptionHandlerKt {
|
|
||||||
void handleCoroutineException(...);
|
|
||||||
}
|
|
||||||
|
|
||||||
# Entry point for the rest of coroutines machinery
|
|
||||||
-keep class kotlinx.coroutines.BuildersKt {
|
|
||||||
** runBlocking(...);
|
|
||||||
** launch(...);
|
|
||||||
}
|
|
||||||
|
|
||||||
# We are cheating a bit by not having android.jar on R8's library classpath. Ignore those warnings.
|
|
||||||
-ignorewarnings
|
|
||||||
|
|
||||||
-keep class kotlinx.coroutines.android.AndroidDispatcherFactory {*;}
|
|
||||||
-keep class kotlinx.coroutines.android.AndroidExceptionPreHandler {*;}
|
|
||||||
|
|
||||||
# Statically turn off all debugging facilities and assertions
|
|
||||||
-keepclassmembers class io.ktor.** { volatile <fields>; }
|
|
||||||
-keep class io.ktor.** { *; }
|
|
||||||
-keep class kotlinx.coroutines.** { *; }
|
|
||||||
-dontwarn kotlinx.atomicfu.**
|
|
||||||
-dontwarn io.netty.**
|
|
||||||
-dontwarn com.typesafe.**
|
|
||||||
-assumenosideeffects class * implements org.slf4j.Logger {
|
|
||||||
public *** trace(...);
|
|
||||||
public *** debug(...);
|
|
||||||
public *** info(...);
|
|
||||||
public *** warn(...);
|
|
||||||
public *** error(...);
|
|
||||||
}
|
|
||||||
-keep class kotlin.reflect.jvm.internal.** { *; }
|
|
||||||
|
|
||||||
-keep class com.arthenica.ffmpegkit.FFmpegKitConfig {
|
|
||||||
native <methods>;
|
|
||||||
void log(long, int, byte[]);
|
|
||||||
void statistics(long, int, float, float, long , int, double, double);
|
|
||||||
int safOpen(int);
|
|
||||||
int safClose(int);
|
|
||||||
}
|
|
||||||
|
|
||||||
-keep class com.arthenica.ffmpegkit.AbiDetect {
|
|
||||||
native <methods>;
|
|
||||||
}
|
|
||||||
|
|
||||||
-keep class com.arthenica.ffmpegkit.NativeLoader { *; }
|
|
||||||
|
|
||||||
-keep class moe.fuqiuluo.** { *; }
|
|
||||||
-keep class com.tencent.** { *; }
|
|
||||||
-keep class com.qq.** { *; }
|
|
||||||
-keep class com.google.gson.** { *; }
|
|
||||||
-keep class de.** { *; }
|
|
||||||
-keep class mqq.** { *; }
|
|
||||||
-keep class QQService.** { *; }
|
|
||||||
-keep class SummaryCard.** { *; }
|
|
||||||
-keep class tencent.** { *; }
|
|
||||||
|
|
||||||
-keepclassmembers class * {
|
|
||||||
native <methods>;
|
|
||||||
}
|
|
||||||
-keep class io.netty.** { *; }
|
|
||||||
|
|
||||||
-keep class moe.fuqiuluo.** implements com.tencent.qqnt.kernel.nativeinterface.** {
|
|
||||||
*;
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
// IByteData.aidl
|
|
||||||
package moe.fuqiuluo.shamrock.xposed.ipc.bytedata;
|
|
||||||
|
|
||||||
import moe.fuqiuluo.shamrock.xposed.ipc.bytedata.IByteDataSign;
|
|
||||||
|
|
||||||
interface IByteData {
|
|
||||||
IByteDataSign sign(String uin, String data, in byte[] salt);
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
// IByteDataSign.aidl
|
|
||||||
package moe.fuqiuluo.shamrock.xposed.ipc.bytedata;
|
|
||||||
|
|
||||||
parcelable IByteDataSign;
|
|
@ -1,4 +0,0 @@
|
|||||||
// IQSign.aidl
|
|
||||||
package moe.fuqiuluo.shamrock.xposed.ipc.qsign;
|
|
||||||
|
|
||||||
parcelable IQSign;
|
|
@ -1,14 +0,0 @@
|
|||||||
// IQSigner.aidl
|
|
||||||
package moe.fuqiuluo.shamrock.xposed.ipc.qsign;
|
|
||||||
|
|
||||||
import moe.fuqiuluo.shamrock.xposed.ipc.qsign.IQSign;
|
|
||||||
|
|
||||||
interface IQSigner {
|
|
||||||
IQSign sign(String cmd, int seq, String uin, in byte[] buffer);
|
|
||||||
|
|
||||||
byte[] energy(String module, in byte[] salt);
|
|
||||||
|
|
||||||
byte[] xwDebugId(String uin, String start, String end);
|
|
||||||
|
|
||||||
List<String> getCmdWhiteList();
|
|
||||||
}
|
|
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=true
|
||||||
|
|
||||||
|
# 反JVM调用栈跟踪
|
||||||
|
anti_jvm_trace=true
|
||||||
|
|
||||||
|
# 调试模式
|
||||||
|
#debug=false
|
@ -173,7 +173,7 @@ NativeOnModuleLoaded native_init(const NativeAPIEntries *entries) {
|
|||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_moe_fuqiuluo_shamrock_xposed_hooks_AntiDetection_antiNativeDetections(JNIEnv *env,
|
Java_moe_fuqiuluo_shamrock_xposed_actions_AntiDetection_antiNativeDetections(JNIEnv *env,
|
||||||
jobject thiz) {
|
jobject thiz) {
|
||||||
if (hook_function == nullptr) return false;
|
if (hook_function == nullptr) return false;
|
||||||
hook_function((void*) __system_property_get, (void *)fake_system_property_get, (void **) &backup_system_property_get);
|
hook_function((void*) __system_property_get, (void *)fake_system_property_get, (void **) &backup_system_property_get);
|
||||||
|
64
xposed/src/main/java/kritor/auth/AuthInterceptor.kt
Normal file
64
xposed/src/main/java/kritor/auth/AuthInterceptor.kt
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package kritor.auth
|
||||||
|
|
||||||
|
import io.grpc.ForwardingServerCallListener
|
||||||
|
import io.grpc.Metadata
|
||||||
|
import io.grpc.ServerCall
|
||||||
|
import io.grpc.ServerCallHandler
|
||||||
|
import io.grpc.ServerInterceptor
|
||||||
|
import moe.fuqiuluo.shamrock.config.ActiveTicket
|
||||||
|
import moe.fuqiuluo.shamrock.config.ShamrockConfig
|
||||||
|
|
||||||
|
object AuthInterceptor: ServerInterceptor {
|
||||||
|
/**
|
||||||
|
* Intercept [ServerCall] dispatch by the `next` [ServerCallHandler]. General
|
||||||
|
* semantics of [ServerCallHandler.startCall] apply and the returned
|
||||||
|
* [io.grpc.ServerCall.Listener] must not be `null`.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* If the implementation throws an exception, `call` will be closed with an error.
|
||||||
|
* Implementations must not throw an exception if they started processing that may use `call` on another thread.
|
||||||
|
*
|
||||||
|
* @param call object to receive response messages
|
||||||
|
* @param headers which can contain extra call metadata from [ClientCall.start],
|
||||||
|
* e.g. authentication credentials.
|
||||||
|
* @param next next processor in the interceptor chain
|
||||||
|
* @return listener for processing incoming messages for `call`, never `null`.
|
||||||
|
*/
|
||||||
|
override fun <ReqT : Any?, RespT : Any?> interceptCall(
|
||||||
|
call: ServerCall<ReqT, RespT>,
|
||||||
|
headers: Metadata?,
|
||||||
|
next: ServerCallHandler<ReqT, RespT>
|
||||||
|
): ServerCall.Listener<ReqT> {
|
||||||
|
val methodName = call.methodDescriptor.fullMethodName
|
||||||
|
val ticket = getAllTicket()
|
||||||
|
if (ticket.isNotEmpty() && !methodName.startsWith("Auth")) {
|
||||||
|
val ticketHeader = headers?.get(Metadata.Key.of("ticket", Metadata.ASCII_STRING_MARSHALLER))
|
||||||
|
if (ticketHeader == null || ticketHeader !in ticket) {
|
||||||
|
call.close(io.grpc.Status.UNAUTHENTICATED.withDescription("Invalid ticket"), Metadata())
|
||||||
|
return object: ServerCall.Listener<ReqT>() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return object: ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT>(next.startCall(call, headers)) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAllTicket(): List<String> {
|
||||||
|
val result = arrayListOf<String>()
|
||||||
|
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 result
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.add(ticket)
|
||||||
|
}
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
164
xposed/src/main/java/kritor/client/KritorClient.kt
Normal file
164
xposed/src/main/java/kritor/client/KritorClient.kt
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
6
xposed/src/main/java/kritor/handlers/GrpcHandlers.kt
Normal file
6
xposed/src/main/java/kritor/handlers/GrpcHandlers.kt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package kritor.handlers
|
||||||
|
|
||||||
|
internal object GrpcHandlers {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
70
xposed/src/main/java/kritor/server/KritorServer.kt
Normal file
70
xposed/src/main/java/kritor/server/KritorServer.kt
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
package kritor.server
|
||||||
|
|
||||||
|
import io.grpc.Grpc
|
||||||
|
import io.grpc.Metadata
|
||||||
|
import io.grpc.InsecureServerCredentials
|
||||||
|
import io.grpc.ServerCall
|
||||||
|
import io.grpc.ServerCallHandler
|
||||||
|
import io.grpc.ServerInterceptor
|
||||||
|
import io.grpc.ForwardingServerCall.SimpleForwardingServerCall;
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.asExecutor
|
||||||
|
import kritor.auth.AuthInterceptor
|
||||||
|
import kritor.service.*
|
||||||
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
|
import moe.fuqiuluo.shamrock.tools.ShamrockVersion
|
||||||
|
import qq.service.ticket.TicketHelper
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
class KritorServer(
|
||||||
|
private val port: Int
|
||||||
|
): CoroutineScope {
|
||||||
|
|
||||||
|
private val serverInterceptor = object : ServerInterceptor {
|
||||||
|
override fun <ReqT, RespT> interceptCall(
|
||||||
|
call: ServerCall<ReqT, RespT>, headers: Metadata, next: ServerCallHandler<ReqT, RespT>
|
||||||
|
): ServerCall.Listener<ReqT> {
|
||||||
|
return next.startCall(object : SimpleForwardingServerCall<ReqT, RespT>(call) {
|
||||||
|
override fun sendHeaders(headers: Metadata?) {
|
||||||
|
headers?.apply {
|
||||||
|
put(Metadata.Key.of("kritor-self-uin", Metadata.ASCII_STRING_MARSHALLER), TicketHelper.getUin())
|
||||||
|
put(Metadata.Key.of("kritor-self-uid", Metadata.ASCII_STRING_MARSHALLER), TicketHelper.getUid())
|
||||||
|
put(Metadata.Key.of("kritor-self-version", Metadata.ASCII_STRING_MARSHALLER), "OpenShamrock-$ShamrockVersion")
|
||||||
|
}
|
||||||
|
super.sendHeaders(headers)
|
||||||
|
}
|
||||||
|
}, headers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create())
|
||||||
|
.executor(Dispatchers.IO.asExecutor())
|
||||||
|
.intercept(AuthInterceptor)
|
||||||
|
.intercept(serverInterceptor)
|
||||||
|
.addService(AuthenticationService)
|
||||||
|
.addService(CoreService)
|
||||||
|
.addService(FriendService)
|
||||||
|
.addService(GroupService)
|
||||||
|
.addService(GroupFileService)
|
||||||
|
.addService(MessageService)
|
||||||
|
.addService(EventService)
|
||||||
|
.addService(WebService)
|
||||||
|
.addService(DeveloperService)
|
||||||
|
.addService(QsignService)
|
||||||
|
.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)
|
||||||
|
}
|
60
xposed/src/main/java/kritor/service/AuthenticationService.kt
Normal file
60
xposed/src/main/java/kritor/service/AuthenticationService.kt
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
101
xposed/src/main/java/kritor/service/CoreService.kt
Normal file
101
xposed/src/main/java/kritor/service/CoreService.kt
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package kritor.service
|
||||||
|
|
||||||
|
import android.util.Base64
|
||||||
|
import com.tencent.mobileqq.app.QQAppInterface
|
||||||
|
import io.grpc.Status
|
||||||
|
import io.grpc.StatusRuntimeException
|
||||||
|
import io.kritor.core.*
|
||||||
|
import moe.fuqiuluo.shamrock.tools.ShamrockVersion
|
||||||
|
import moe.fuqiuluo.shamrock.utils.DownloadUtils
|
||||||
|
import moe.fuqiuluo.shamrock.utils.FileUtils
|
||||||
|
import moe.fuqiuluo.shamrock.utils.MD5
|
||||||
|
import mqq.app.MobileQQ
|
||||||
|
import qq.service.QQInterfaces.Companion.app
|
||||||
|
import qq.service.contact.ContactHelper
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
internal object CoreService : CoreServiceGrpcKt.CoreServiceCoroutineImplBase() {
|
||||||
|
@Grpc("CoreService", "GetVersion")
|
||||||
|
override suspend fun getVersion(request: GetVersionRequest): GetVersionResponse {
|
||||||
|
return GetVersionResponse.newBuilder().apply {
|
||||||
|
this.version = ShamrockVersion
|
||||||
|
this.appName = "Shamrock"
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("CoreService", "GetCurrentAccount")
|
||||||
|
override suspend fun getCurrentAccount(request: GetCurrentAccountRequest): GetCurrentAccountResponse {
|
||||||
|
return GetCurrentAccountResponse.newBuilder().apply {
|
||||||
|
this.accountName = if (app is QQAppInterface) app.currentNickname else "unknown"
|
||||||
|
this.accountUid = app.currentUid ?: ""
|
||||||
|
this.accountUin = (app.currentUin ?: "0").toLong()
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("CoreService", "DownloadFile")
|
||||||
|
override suspend fun downloadFile(request: DownloadFileRequest): DownloadFileResponse {
|
||||||
|
val headerMap = mutableMapOf(
|
||||||
|
"User-Agent" to "Shamrock"
|
||||||
|
)
|
||||||
|
if (request.hasHeaders()) {
|
||||||
|
request.headers.split("[\r\n]").forEach {
|
||||||
|
val pair = it.split("=")
|
||||||
|
if (pair.size >= 2) {
|
||||||
|
val (k, v) = pair
|
||||||
|
headerMap[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tmp = FileUtils.getTmpFile("cache")
|
||||||
|
if (request.hasBase64()) {
|
||||||
|
val bytes = Base64.decode(request.base64, Base64.DEFAULT)
|
||||||
|
tmp.writeBytes(bytes)
|
||||||
|
} else if (request.hasUrl()) {
|
||||||
|
if (!DownloadUtils.download(
|
||||||
|
urlAdr = request.url,
|
||||||
|
dest = tmp,
|
||||||
|
headers = headerMap,
|
||||||
|
threadCount = if (request.hasThreadCnt()) request.threadCnt else 3
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw StatusRuntimeException(Status.INTERNAL.withDescription("download failed"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tmp = if (!request.hasFileName()) FileUtils.renameByMd5(tmp)
|
||||||
|
else tmp.parentFile!!.resolve(request.fileName).also {
|
||||||
|
tmp.renameTo(it)
|
||||||
|
}
|
||||||
|
if (request.hasRootPath()) {
|
||||||
|
tmp = File(request.rootPath).resolve(tmp.name).also {
|
||||||
|
tmp.renameTo(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DownloadFileResponse.newBuilder().apply {
|
||||||
|
this.fileMd5 = MD5.genFileMd5Hex(tmp.absolutePath)
|
||||||
|
this.fileAbsolutePath = tmp.absolutePath
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("CoreService", "SwitchAccount")
|
||||||
|
override suspend fun switchAccount(request: SwitchAccountRequest): SwitchAccountResponse {
|
||||||
|
val uin = when (request.accountCase!!) {
|
||||||
|
SwitchAccountRequest.AccountCase.ACCOUNT_UID -> ContactHelper.getUinByUidAsync(request.accountUid)
|
||||||
|
SwitchAccountRequest.AccountCase.ACCOUNT_UIN -> request.accountUin.toString()
|
||||||
|
SwitchAccountRequest.AccountCase.ACCOUNT_NOT_SET -> throw StatusRuntimeException(
|
||||||
|
Status.INVALID_ARGUMENT.withDescription(
|
||||||
|
"account not found"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val account = MobileQQ.getMobileQQ().allAccounts.firstOrNull { it.uin == uin }
|
||||||
|
?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("account not found"))
|
||||||
|
runCatching {
|
||||||
|
app.switchAccount(account, null)
|
||||||
|
}.onFailure {
|
||||||
|
throw StatusRuntimeException(Status.INTERNAL.withCause(it).withDescription("failed to switch account"))
|
||||||
|
}
|
||||||
|
return SwitchAccountResponse.newBuilder().build()
|
||||||
|
}
|
||||||
|
}
|
67
xposed/src/main/java/kritor/service/DeveloperService.kt
Normal file
67
xposed/src/main/java/kritor/service/DeveloperService.kt
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
43
xposed/src/main/java/kritor/service/EventService.kt
Normal file
43
xposed/src/main/java/kritor/service/EventService.kt
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package kritor.service
|
||||||
|
|
||||||
|
import io.grpc.Status
|
||||||
|
import io.grpc.StatusRuntimeException
|
||||||
|
import io.kritor.event.EventServiceGrpcKt
|
||||||
|
import io.kritor.event.EventStructure
|
||||||
|
import io.kritor.event.EventType
|
||||||
|
import io.kritor.event.RequestPushEvent
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.channelFlow
|
||||||
|
import moe.fuqiuluo.shamrock.internals.GlobalEventTransmitter
|
||||||
|
|
||||||
|
internal object EventService : EventServiceGrpcKt.EventServiceCoroutineImplBase() {
|
||||||
|
override fun registerActiveListener(request: RequestPushEvent): Flow<EventStructure> {
|
||||||
|
return channelFlow {
|
||||||
|
when (request.type!!) {
|
||||||
|
EventType.EVENT_TYPE_CORE_EVENT -> {}
|
||||||
|
EventType.EVENT_TYPE_MESSAGE -> GlobalEventTransmitter.onMessageEvent {
|
||||||
|
send(EventStructure.newBuilder().apply {
|
||||||
|
this.type = EventType.EVENT_TYPE_MESSAGE
|
||||||
|
this.message = it.second
|
||||||
|
}.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
EventType.EVENT_TYPE_NOTICE -> GlobalEventTransmitter.onRequestEvent {
|
||||||
|
send(EventStructure.newBuilder().apply {
|
||||||
|
this.type = EventType.EVENT_TYPE_NOTICE
|
||||||
|
this.request = it
|
||||||
|
}.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
EventType.EVENT_TYPE_REQUEST -> GlobalEventTransmitter.onNoticeEvent {
|
||||||
|
send(EventStructure.newBuilder().apply {
|
||||||
|
this.type = EventType.EVENT_TYPE_NOTICE
|
||||||
|
this.notice = it
|
||||||
|
}.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
EventType.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
244
xposed/src/main/java/kritor/service/FriendService.kt
Normal file
244
xposed/src/main/java/kritor/service/FriendService.kt
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
package kritor.service
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import com.tencent.mobileqq.profilecard.api.IProfileCardBlacklistApi
|
||||||
|
import com.tencent.mobileqq.profilecard.api.IProfileProtocolConst
|
||||||
|
import com.tencent.mobileqq.profilecard.api.IProfileProtocolService
|
||||||
|
import com.tencent.mobileqq.qroute.QRoute
|
||||||
|
import io.grpc.Status
|
||||||
|
import io.grpc.StatusRuntimeException
|
||||||
|
import io.kritor.friend.*
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
|
import qq.service.QQInterfaces
|
||||||
|
import qq.service.contact.ContactHelper
|
||||||
|
import qq.service.friend.FriendHelper
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
|
internal object FriendService : FriendServiceGrpcKt.FriendServiceCoroutineImplBase() {
|
||||||
|
@Grpc("FriendService", "GetFriendList")
|
||||||
|
override suspend fun getFriendList(request: GetFriendListRequest): GetFriendListResponse {
|
||||||
|
val friendList = FriendHelper.getFriendList(if (request.hasRefresh()) request.refresh else false).onFailure {
|
||||||
|
throw StatusRuntimeException(
|
||||||
|
Status.INTERNAL
|
||||||
|
.withDescription(it.stackTraceToString())
|
||||||
|
)
|
||||||
|
}.getOrThrow()
|
||||||
|
|
||||||
|
return GetFriendListResponse.newBuilder().apply {
|
||||||
|
friendList.forEach {
|
||||||
|
this.addFriendsInfo(FriendInfo.newBuilder().apply {
|
||||||
|
uin = it.uin.toLong()
|
||||||
|
uid = ContactHelper.getUidByUinAsync(uin)
|
||||||
|
qid = ""
|
||||||
|
nick = it.name ?: ""
|
||||||
|
remark = it.remark ?: ""
|
||||||
|
age = it.age
|
||||||
|
level = 0
|
||||||
|
gender = it.gender.toInt()
|
||||||
|
groupId = it.groupid
|
||||||
|
|
||||||
|
ext = ExtInfo.newBuilder().build()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("FriendService", "GetFriendProfileCard")
|
||||||
|
override suspend fun getFriendProfileCard(request: GetFriendProfileCardRequest): GetFriendProfileCardResponse {
|
||||||
|
return GetFriendProfileCardResponse.newBuilder().apply {
|
||||||
|
request.targetUinsList.forEach {
|
||||||
|
ContactHelper.getProfileCard(it).getOrThrow().let { info ->
|
||||||
|
addFriendsProfileCard(ProfileCard.newBuilder().apply {
|
||||||
|
this.uin = info.uin.toLong()
|
||||||
|
this.uid = ContactHelper.getUidByUinAsync(info.uin.toLong())
|
||||||
|
this.nick = info.strNick
|
||||||
|
this.remark = info.strReMark
|
||||||
|
this.level = info.iQQLevel
|
||||||
|
this.birthday = info.lBirthday
|
||||||
|
this.loginDay = info.lLoginDays.toInt()
|
||||||
|
this.voteCnt = info.lVoteCount.toInt()
|
||||||
|
this.qid = info.qid ?: ""
|
||||||
|
this.isSchoolVerified = info.schoolVerifiedFlag
|
||||||
|
|
||||||
|
this.ext = ExtInfo.newBuilder().apply {
|
||||||
|
this.bigVip = info.bBigClubVipOpen == 1.toByte()
|
||||||
|
this.hollywoodVip = info.bHollywoodVipOpen == 1.toByte()
|
||||||
|
this.qqVip = info.bQQVipOpen == 1.toByte()
|
||||||
|
this.superVip = info.bSuperQQOpen == 1.toByte()
|
||||||
|
this.voted = info.bVoted == 1.toByte()
|
||||||
|
}.build()
|
||||||
|
}.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.targetUidsList.forEach {
|
||||||
|
ContactHelper.getProfileCard(ContactHelper.getUinByUidAsync(it).toLong()).getOrThrow().let { info ->
|
||||||
|
addFriendsProfileCard(ProfileCard.newBuilder().apply {
|
||||||
|
this.uin = info.uin.toLong()
|
||||||
|
this.uid = it
|
||||||
|
this.nick = info.strNick
|
||||||
|
this.remark = info.strReMark
|
||||||
|
this.level = info.iQQLevel
|
||||||
|
this.birthday = info.lBirthday
|
||||||
|
this.loginDay = info.lLoginDays.toInt()
|
||||||
|
this.voteCnt = info.lVoteCount.toInt()
|
||||||
|
this.qid = info.qid ?: ""
|
||||||
|
this.isSchoolVerified = info.schoolVerifiedFlag
|
||||||
|
|
||||||
|
this.ext = ExtInfo.newBuilder().apply {
|
||||||
|
this.bigVip = info.bBigClubVipOpen == 1.toByte()
|
||||||
|
this.hollywoodVip = info.bHollywoodVipOpen == 1.toByte()
|
||||||
|
this.qqVip = info.bQQVipOpen == 1.toByte()
|
||||||
|
this.superVip = info.bSuperQQOpen == 1.toByte()
|
||||||
|
this.voted = info.bVoted == 1.toByte()
|
||||||
|
}.build()
|
||||||
|
}.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("FriendService", "GetStrangerProfileCard")
|
||||||
|
override suspend fun getStrangerProfileCard(request: GetStrangerProfileCardRequest): GetStrangerProfileCardResponse {
|
||||||
|
return GetStrangerProfileCardResponse.newBuilder().apply {
|
||||||
|
request.targetUinsList.forEach {
|
||||||
|
ContactHelper.refreshAndGetProfileCard(it).getOrThrow().let { info ->
|
||||||
|
addStrangersProfileCard(ProfileCard.newBuilder().apply {
|
||||||
|
this.uin = info.uin.toLong()
|
||||||
|
this.uid = ContactHelper.getUidByUinAsync(info.uin.toLong())
|
||||||
|
this.nick = info.strNick
|
||||||
|
this.level = info.iQQLevel
|
||||||
|
this.birthday = info.lBirthday
|
||||||
|
this.loginDay = info.lLoginDays.toInt()
|
||||||
|
this.voteCnt = info.lVoteCount.toInt()
|
||||||
|
this.qid = info.qid ?: ""
|
||||||
|
this.isSchoolVerified = info.schoolVerifiedFlag
|
||||||
|
|
||||||
|
this.ext = ExtInfo.newBuilder().apply {
|
||||||
|
this.bigVip = info.bBigClubVipOpen == 1.toByte()
|
||||||
|
this.hollywoodVip = info.bHollywoodVipOpen == 1.toByte()
|
||||||
|
this.qqVip = info.bQQVipOpen == 1.toByte()
|
||||||
|
this.superVip = info.bSuperQQOpen == 1.toByte()
|
||||||
|
this.voted = info.bVoted == 1.toByte()
|
||||||
|
}.build()
|
||||||
|
}.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.targetUidsList.forEach {
|
||||||
|
ContactHelper.refreshAndGetProfileCard(ContactHelper.getUinByUidAsync(it).toLong()).getOrThrow()
|
||||||
|
.let { info ->
|
||||||
|
addStrangersProfileCard(ProfileCard.newBuilder().apply {
|
||||||
|
this.uin = info.uin.toLong()
|
||||||
|
this.uid = it
|
||||||
|
this.nick = info.strNick
|
||||||
|
this.level = info.iQQLevel
|
||||||
|
this.birthday = info.lBirthday
|
||||||
|
this.loginDay = info.lLoginDays.toInt()
|
||||||
|
this.voteCnt = info.lVoteCount.toInt()
|
||||||
|
this.qid = info.qid ?: ""
|
||||||
|
this.isSchoolVerified = info.schoolVerifiedFlag
|
||||||
|
|
||||||
|
this.ext = ExtInfo.newBuilder().apply {
|
||||||
|
this.bigVip = info.bBigClubVipOpen == 1.toByte()
|
||||||
|
this.hollywoodVip = info.bHollywoodVipOpen == 1.toByte()
|
||||||
|
this.qqVip = info.bQQVipOpen == 1.toByte()
|
||||||
|
this.superVip = info.bSuperQQOpen == 1.toByte()
|
||||||
|
this.voted = info.bVoted == 1.toByte()
|
||||||
|
}.build()
|
||||||
|
}.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("FriendService", "SetProfileCard")
|
||||||
|
override suspend fun setProfileCard(request: SetProfileCardRequest): SetProfileCardResponse {
|
||||||
|
val bundle = Bundle()
|
||||||
|
val service = QQInterfaces.app
|
||||||
|
.getRuntimeService(IProfileProtocolService::class.java, "all")
|
||||||
|
if (request.hasNickName()) {
|
||||||
|
bundle.putString(IProfileProtocolConst.KEY_NICK, request.nickName)
|
||||||
|
}
|
||||||
|
if (request.hasCompany()) {
|
||||||
|
bundle.putString(IProfileProtocolConst.KEY_COMPANY, request.company)
|
||||||
|
}
|
||||||
|
if (request.hasEmail()) {
|
||||||
|
bundle.putString(IProfileProtocolConst.KEY_EMAIL, request.email)
|
||||||
|
}
|
||||||
|
if (request.hasCollege()) {
|
||||||
|
bundle.putString(IProfileProtocolConst.KEY_COLLEGE, request.college)
|
||||||
|
}
|
||||||
|
if (request.hasPersonalNote()) {
|
||||||
|
bundle.putString(IProfileProtocolConst.KEY_PERSONAL_NOTE, request.personalNote)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.hasBirthday()) {
|
||||||
|
bundle.putInt(IProfileProtocolConst.KEY_BIRTHDAY, request.birthday)
|
||||||
|
}
|
||||||
|
if (request.hasAge()) {
|
||||||
|
bundle.putInt(IProfileProtocolConst.KEY_AGE, request.age)
|
||||||
|
}
|
||||||
|
|
||||||
|
service.setProfileDetail(bundle)
|
||||||
|
return super.setProfileCard(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("FriendService", "IsBlackListUser")
|
||||||
|
override suspend fun isBlackListUser(request: IsBlackListUserRequest): IsBlackListUserResponse {
|
||||||
|
val uin = when (request.targetCase!!) {
|
||||||
|
IsBlackListUserRequest.TargetCase.TARGET_UIN -> request.targetUin.toString()
|
||||||
|
IsBlackListUserRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid)
|
||||||
|
IsBlackListUserRequest.TargetCase.TARGET_NOT_SET -> throw StatusRuntimeException(
|
||||||
|
Status.INVALID_ARGUMENT
|
||||||
|
.withDescription("account not set")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val blacklistApi = QRoute.api(IProfileCardBlacklistApi::class.java)
|
||||||
|
val isBlack = withTimeoutOrNull(5000) {
|
||||||
|
suspendCancellableCoroutine { continuation ->
|
||||||
|
blacklistApi.isBlackOrBlackedUin(uin) {
|
||||||
|
continuation.resume(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ?: false
|
||||||
|
return IsBlackListUserResponse.newBuilder().setIsBlackListUser(isBlack).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("FriendService", "VoteUser")
|
||||||
|
override suspend fun voteUser(request: VoteUserRequest): VoteUserResponse {
|
||||||
|
ContactHelper.voteUser(
|
||||||
|
when (request.targetCase!!) {
|
||||||
|
VoteUserRequest.TargetCase.TARGET_UIN -> request.targetUin
|
||||||
|
VoteUserRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
|
||||||
|
VoteUserRequest.TargetCase.TARGET_NOT_SET -> throw StatusRuntimeException(
|
||||||
|
Status.INVALID_ARGUMENT
|
||||||
|
.withDescription("account not set")
|
||||||
|
)
|
||||||
|
}, request.voteCount
|
||||||
|
).onFailure {
|
||||||
|
throw StatusRuntimeException(
|
||||||
|
Status.INTERNAL
|
||||||
|
.withDescription(it.stackTraceToString())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return VoteUserResponse.newBuilder().build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("FriendService", "GetUidByUin")
|
||||||
|
override suspend fun getUidByUin(request: GetUidByUinRequest): GetUidByUinResponse {
|
||||||
|
return GetUidByUinResponse.newBuilder().apply {
|
||||||
|
request.targetUinsList.forEach {
|
||||||
|
putUidMap(it, ContactHelper.getUidByUinAsync(it))
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("FriendService", "GetUinByUid")
|
||||||
|
override suspend fun getUinByUid(request: GetUinByUidRequest): GetUinByUidResponse {
|
||||||
|
return GetUinByUidResponse.newBuilder().apply {
|
||||||
|
request.targetUidsList.forEach {
|
||||||
|
putUinMap(it, ContactHelper.getUinByUidAsync(it).toLong())
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
}
|
136
xposed/src/main/java/kritor/service/GroupFileService.kt
Normal file
136
xposed/src/main/java/kritor/service/GroupFileService.kt
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
package kritor.service
|
||||||
|
|
||||||
|
import io.grpc.Status
|
||||||
|
import io.grpc.StatusRuntimeException
|
||||||
|
import io.kritor.file.*
|
||||||
|
import moe.fuqiuluo.shamrock.tools.slice
|
||||||
|
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||||
|
import protobuf.auto.toByteArray
|
||||||
|
import protobuf.oidb.cmd0x6d7.CreateFolderReq
|
||||||
|
import protobuf.oidb.cmd0x6d7.DeleteFolderReq
|
||||||
|
import protobuf.oidb.cmd0x6d7.Oidb0x6d7ReqBody
|
||||||
|
import protobuf.oidb.cmd0x6d7.Oidb0x6d7RespBody
|
||||||
|
import protobuf.oidb.cmd0x6d7.RenameFolderReq
|
||||||
|
import qq.service.QQInterfaces
|
||||||
|
import qq.service.file.GroupFileHelper
|
||||||
|
import qq.service.file.GroupFileHelper.getGroupFileSystemInfo
|
||||||
|
import tencent.im.oidb.cmd0x6d6.oidb_0x6d6
|
||||||
|
import tencent.im.oidb.oidb_sso
|
||||||
|
|
||||||
|
internal object GroupFileService : GroupFileServiceGrpcKt.GroupFileServiceCoroutineImplBase() {
|
||||||
|
@Grpc("GroupFileService", "CreateFolder")
|
||||||
|
override suspend fun createFolder(request: CreateFolderRequest): CreateFolderResponse {
|
||||||
|
val data = Oidb0x6d7ReqBody(
|
||||||
|
createFolder = CreateFolderReq(
|
||||||
|
groupCode = request.groupId.toULong(),
|
||||||
|
appId = 3u,
|
||||||
|
parentFolderId = "/",
|
||||||
|
folderName = request.name
|
||||||
|
)
|
||||||
|
).toByteArray()
|
||||||
|
val fromServiceMsg = QQInterfaces.sendOidbAW("OidbSvc.0x6d7_0", 1751, 0, data)
|
||||||
|
?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
|
||||||
|
if (fromServiceMsg.wupBuffer == null) {
|
||||||
|
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
||||||
|
}
|
||||||
|
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
||||||
|
oidbPkg.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||||
|
val rsp = oidbPkg.bytes_bodybuffer.get()
|
||||||
|
.toByteArray()
|
||||||
|
.decodeProtobuf<Oidb0x6d7RespBody>()
|
||||||
|
if (rsp.createFolder?.retCode != 0) {
|
||||||
|
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to create folder: ${rsp.createFolder?.retCode}"))
|
||||||
|
}
|
||||||
|
return CreateFolderResponse.newBuilder().apply {
|
||||||
|
this.id = rsp.createFolder?.folderInfo?.folderId ?: ""
|
||||||
|
this.usedSpace = 0
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("GroupFileService", "DeleteFolder")
|
||||||
|
override suspend fun deleteFolder(request: DeleteFolderRequest): DeleteFolderResponse {
|
||||||
|
val fromServiceMsg = QQInterfaces.sendOidbAW(
|
||||||
|
"OidbSvc.0x6d7_1", 1751, 1, Oidb0x6d7ReqBody(
|
||||||
|
deleteFolder = DeleteFolderReq(
|
||||||
|
groupCode = request.groupId.toULong(),
|
||||||
|
appId = 3u,
|
||||||
|
folderId = request.folderId
|
||||||
|
)
|
||||||
|
).toByteArray()
|
||||||
|
) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
|
||||||
|
if (fromServiceMsg.wupBuffer == null) {
|
||||||
|
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
||||||
|
}
|
||||||
|
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
||||||
|
oidbPkg.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||||
|
val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>()
|
||||||
|
if (rsp.deleteFolder?.retCode != 0) {
|
||||||
|
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to delete folder: ${rsp.deleteFolder?.retCode}"))
|
||||||
|
}
|
||||||
|
return DeleteFolderResponse.newBuilder().build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("GroupFileService", "DeleteFile")
|
||||||
|
override suspend fun deleteFile(request: DeleteFileRequest): DeleteFileResponse {
|
||||||
|
val oidb0x6d6ReqBody = oidb_0x6d6.ReqBody().apply {
|
||||||
|
delete_file_req.set(oidb_0x6d6.DeleteFileReqBody().apply {
|
||||||
|
uint64_group_code.set(request.groupId)
|
||||||
|
uint32_app_id.set(3)
|
||||||
|
uint32_bus_id.set(request.busId)
|
||||||
|
str_parent_folder_id.set("/")
|
||||||
|
str_file_id.set(request.fileId)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
val fromServiceMsg = QQInterfaces.sendOidbAW("OidbSvc.0x6d6_3", 1750, 3, oidb0x6d6ReqBody.toByteArray())
|
||||||
|
?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
|
||||||
|
if (fromServiceMsg.wupBuffer == null) {
|
||||||
|
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
||||||
|
}
|
||||||
|
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
||||||
|
oidbPkg.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||||
|
val rsp = oidb_0x6d6.RspBody().apply {
|
||||||
|
mergeFrom(oidbPkg.bytes_bodybuffer.get().toByteArray())
|
||||||
|
}
|
||||||
|
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()}"))
|
||||||
|
}
|
||||||
|
return DeleteFileResponse.newBuilder().build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("GroupFileService", "RenameFolder")
|
||||||
|
override suspend fun renameFolder(request: RenameFolderRequest): RenameFolderResponse {
|
||||||
|
val fromServiceMsg = QQInterfaces.sendOidbAW(
|
||||||
|
"OidbSvc.0x6d7_3", 1751, 3, Oidb0x6d7ReqBody(
|
||||||
|
renameFolder = RenameFolderReq(
|
||||||
|
groupCode = request.groupId.toULong(),
|
||||||
|
appId = 3u,
|
||||||
|
folderId = request.folderId,
|
||||||
|
folderName = request.name
|
||||||
|
)
|
||||||
|
).toByteArray()
|
||||||
|
) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
|
||||||
|
if (fromServiceMsg.wupBuffer == null) {
|
||||||
|
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
||||||
|
}
|
||||||
|
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
||||||
|
oidbPkg.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||||
|
val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>()
|
||||||
|
if (rsp.renameFolder?.retCode != 0) {
|
||||||
|
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to rename folder: ${rsp.renameFolder?.retCode}"))
|
||||||
|
}
|
||||||
|
return RenameFolderResponse.newBuilder().build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("GroupFileService", "GetFileSystemInfo")
|
||||||
|
override suspend fun getFileSystemInfo(request: GetFileSystemInfoRequest): GetFileSystemInfoResponse {
|
||||||
|
return getGroupFileSystemInfo(request.groupId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("GroupFileService", "GetFileList")
|
||||||
|
override suspend fun getFileList(request: GetFileListRequest): GetFileListResponse {
|
||||||
|
return if (request.hasFolderId())
|
||||||
|
GroupFileHelper.getGroupFiles(request.groupId, request.folderId)
|
||||||
|
else
|
||||||
|
GroupFileHelper.getGroupFiles(request.groupId)
|
||||||
|
}
|
||||||
|
}
|
403
xposed/src/main/java/kritor/service/GroupService.kt
Normal file
403
xposed/src/main/java/kritor/service/GroupService.kt
Normal file
@ -0,0 +1,403 @@
|
|||||||
|
package kritor.service
|
||||||
|
|
||||||
|
import io.grpc.Status
|
||||||
|
import io.grpc.StatusRuntimeException
|
||||||
|
import io.kritor.group.*
|
||||||
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
|
import moe.fuqiuluo.shamrock.helper.TroopHonorHelper.decodeHonor
|
||||||
|
import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
|
||||||
|
import qq.service.contact.ContactHelper
|
||||||
|
import qq.service.group.GroupHelper
|
||||||
|
|
||||||
|
internal object GroupService : GroupServiceGrpcKt.GroupServiceCoroutineImplBase() {
|
||||||
|
@Grpc("GroupService", "BanMember")
|
||||||
|
override suspend fun banMember(request: BanMemberRequest): BanMemberResponse {
|
||||||
|
if (!GroupHelper.isAdmin(request.groupId.toString())) {
|
||||||
|
throw StatusRuntimeException(
|
||||||
|
Status.PERMISSION_DENIED
|
||||||
|
.withDescription("You are not admin of this group")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupHelper.banMember(
|
||||||
|
request.groupId, when (request.targetCase!!) {
|
||||||
|
BanMemberRequest.TargetCase.TARGET_UIN -> request.targetUin
|
||||||
|
BanMemberRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
|
||||||
|
else -> throw StatusRuntimeException(
|
||||||
|
Status.INVALID_ARGUMENT
|
||||||
|
.withDescription("target not set")
|
||||||
|
)
|
||||||
|
}, request.duration
|
||||||
|
)
|
||||||
|
|
||||||
|
return BanMemberResponse.newBuilder().apply {
|
||||||
|
groupId = request.groupId
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("GroupService", "PokeMember")
|
||||||
|
override suspend fun pokeMember(request: PokeMemberRequest): PokeMemberResponse {
|
||||||
|
GroupHelper.pokeMember(
|
||||||
|
request.groupId, when (request.targetCase!!) {
|
||||||
|
PokeMemberRequest.TargetCase.TARGET_UIN -> request.targetUin
|
||||||
|
PokeMemberRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
|
||||||
|
else -> throw StatusRuntimeException(
|
||||||
|
Status.INVALID_ARGUMENT
|
||||||
|
.withDescription("target not set")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return PokeMemberResponse.newBuilder().build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("GroupService", "KickMember")
|
||||||
|
override suspend fun kickMember(request: KickMemberRequest): KickMemberResponse {
|
||||||
|
if (!GroupHelper.isAdmin(request.groupId.toString())) {
|
||||||
|
throw StatusRuntimeException(
|
||||||
|
Status.PERMISSION_DENIED
|
||||||
|
.withDescription("You are not admin of this group")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
GroupHelper.kickMember(
|
||||||
|
request.groupId,
|
||||||
|
request.rejectAddRequest,
|
||||||
|
if (request.hasKickReason()) request.kickReason else "",
|
||||||
|
when (request.targetCase!!) {
|
||||||
|
KickMemberRequest.TargetCase.TARGET_UIN -> request.targetUin
|
||||||
|
KickMemberRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
|
||||||
|
else -> throw StatusRuntimeException(
|
||||||
|
Status.INVALID_ARGUMENT
|
||||||
|
.withDescription("target not set")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return KickMemberResponse.newBuilder().build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("GroupService", "LeaveGroup")
|
||||||
|
override suspend fun leaveGroup(request: LeaveGroupRequest): LeaveGroupResponse {
|
||||||
|
GroupHelper.resignTroop(request.groupId.toString())
|
||||||
|
return LeaveGroupResponse.newBuilder().build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("GroupService", "ModifyMemberCard")
|
||||||
|
override suspend fun modifyMemberCard(request: ModifyMemberCardRequest): ModifyMemberCardResponse {
|
||||||
|
if (!GroupHelper.isAdmin(request.groupId.toString())) {
|
||||||
|
throw StatusRuntimeException(
|
||||||
|
Status.PERMISSION_DENIED
|
||||||
|
.withDescription("You are not admin of this group")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
GroupHelper.modifyGroupMemberCard(
|
||||||
|
request.groupId, when (request.targetCase!!) {
|
||||||
|
ModifyMemberCardRequest.TargetCase.TARGET_UIN -> request.targetUin
|
||||||
|
ModifyMemberCardRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid)
|
||||||
|
.toLong()
|
||||||
|
|
||||||
|
else -> throw StatusRuntimeException(
|
||||||
|
Status.INVALID_ARGUMENT
|
||||||
|
.withDescription("target not set")
|
||||||
|
)
|
||||||
|
}, request.card
|
||||||
|
)
|
||||||
|
return ModifyMemberCardResponse.newBuilder().build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("GroupService", "ModifyGroupName")
|
||||||
|
override suspend fun modifyGroupName(request: ModifyGroupNameRequest): ModifyGroupNameResponse {
|
||||||
|
if (!GroupHelper.isAdmin(request.groupId.toString())) {
|
||||||
|
throw StatusRuntimeException(
|
||||||
|
Status.PERMISSION_DENIED
|
||||||
|
.withDescription("You are not admin of this group")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupHelper.modifyTroopName(request.groupId.toString(), request.groupName)
|
||||||
|
|
||||||
|
return ModifyGroupNameResponse.newBuilder().build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("GroupService", "ModifyGroupRemark")
|
||||||
|
override suspend fun modifyGroupRemark(request: ModifyGroupRemarkRequest): ModifyGroupRemarkResponse {
|
||||||
|
GroupHelper.modifyGroupRemark(request.groupId, request.remark)
|
||||||
|
|
||||||
|
return ModifyGroupRemarkResponse.newBuilder().build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("GroupService", "SetGroupAdmin")
|
||||||
|
override suspend fun setGroupAdmin(request: SetGroupAdminRequest): SetGroupAdminResponse {
|
||||||
|
if (!GroupHelper.isOwner(request.groupId.toString())) {
|
||||||
|
throw StatusRuntimeException(
|
||||||
|
Status.PERMISSION_DENIED
|
||||||
|
.withDescription("You are not admin of this group")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupHelper.setGroupAdmin(
|
||||||
|
request.groupId, when (request.targetCase!!) {
|
||||||
|
SetGroupAdminRequest.TargetCase.TARGET_UIN -> request.targetUin
|
||||||
|
SetGroupAdminRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
|
||||||
|
else -> throw StatusRuntimeException(
|
||||||
|
Status.INVALID_ARGUMENT
|
||||||
|
.withDescription("target not set")
|
||||||
|
)
|
||||||
|
}, request.isAdmin
|
||||||
|
)
|
||||||
|
|
||||||
|
return SetGroupAdminResponse.newBuilder().build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("GroupService", "SetGroupUniqueTitle")
|
||||||
|
override suspend fun setGroupUniqueTitle(request: SetGroupUniqueTitleRequest): SetGroupUniqueTitleResponse {
|
||||||
|
if (!GroupHelper.isAdmin(request.groupId.toString())) {
|
||||||
|
throw StatusRuntimeException(
|
||||||
|
Status.PERMISSION_DENIED
|
||||||
|
.withDescription("You are not admin of this group")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupHelper.setGroupUniqueTitle(
|
||||||
|
request.groupId.toString(), when (request.targetCase!!) {
|
||||||
|
SetGroupUniqueTitleRequest.TargetCase.TARGET_UIN -> request.targetUin
|
||||||
|
SetGroupUniqueTitleRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid)
|
||||||
|
.toLong()
|
||||||
|
|
||||||
|
else -> throw StatusRuntimeException(
|
||||||
|
Status.INVALID_ARGUMENT
|
||||||
|
.withDescription("target not set")
|
||||||
|
)
|
||||||
|
}.toString(), request.uniqueTitle
|
||||||
|
)
|
||||||
|
|
||||||
|
return SetGroupUniqueTitleResponse.newBuilder().build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("GroupService", "SetGroupWholeBan")
|
||||||
|
override suspend fun setGroupWholeBan(request: SetGroupWholeBanRequest): SetGroupWholeBanResponse {
|
||||||
|
if (!GroupHelper.isAdmin(request.groupId.toString())) {
|
||||||
|
throw StatusRuntimeException(
|
||||||
|
Status.PERMISSION_DENIED
|
||||||
|
.withDescription("You are not admin of this group")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupHelper.setGroupWholeBan(request.groupId, request.isBan)
|
||||||
|
return SetGroupWholeBanResponse.newBuilder().build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("GroupService", "GetGroupInfo")
|
||||||
|
override suspend fun getGroupInfo(request: GetGroupInfoRequest): GetGroupInfoResponse {
|
||||||
|
val groupInfo = GroupHelper.getGroupInfo(request.groupId.toString(), true).onFailure {
|
||||||
|
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get group info").withCause(it))
|
||||||
|
}.getOrThrow()
|
||||||
|
return GetGroupInfoResponse.newBuilder().apply {
|
||||||
|
this.groupInfo = GroupInfo.newBuilder().apply {
|
||||||
|
groupId = groupInfo.troopcode.toLong()
|
||||||
|
groupName =
|
||||||
|
groupInfo.troopname.ifNullOrEmpty { groupInfo.troopRemark }.ifNullOrEmpty { groupInfo.newTroopName }
|
||||||
|
?: ""
|
||||||
|
groupRemark = groupInfo.troopRemark ?: ""
|
||||||
|
owner = groupInfo.troopowneruin?.toLong() ?: 0
|
||||||
|
addAllAdmins(GroupHelper.getAdminList(groupId))
|
||||||
|
maxMemberCount = groupInfo.wMemberMax
|
||||||
|
memberCount = groupInfo.wMemberNum
|
||||||
|
groupUin = groupInfo.troopuin?.toLong() ?: 0
|
||||||
|
}.build()
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("GroupService", "GetGroupList")
|
||||||
|
override suspend fun getGroupList(request: GetGroupListRequest): GetGroupListResponse {
|
||||||
|
val groupList = GroupHelper.getGroupList(if (request.hasRefresh()) request.refresh else false).onFailure {
|
||||||
|
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get group list").withCause(it))
|
||||||
|
}.getOrThrow()
|
||||||
|
return GetGroupListResponse.newBuilder().apply {
|
||||||
|
groupList.forEach { groupInfo ->
|
||||||
|
this.addGroupsInfo(GroupInfo.newBuilder().apply {
|
||||||
|
groupId = groupInfo.troopcode.ifNullOrEmpty { groupInfo.uin }.ifNullOrEmpty { groupInfo.troopuin }?.toLong() ?: 0
|
||||||
|
groupName = groupInfo.troopname.ifNullOrEmpty { groupInfo.troopRemark }
|
||||||
|
.ifNullOrEmpty { groupInfo.newTroopName }
|
||||||
|
?: ""
|
||||||
|
groupRemark = groupInfo.troopRemark ?: ""
|
||||||
|
owner = groupInfo.troopowneruin?.toLong() ?: 0
|
||||||
|
addAllAdmins(GroupHelper.getAdminList(groupId))
|
||||||
|
maxMemberCount = groupInfo.wMemberMax
|
||||||
|
memberCount = groupInfo.wMemberNum
|
||||||
|
groupUin = groupInfo.troopuin?.toLong() ?: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("GroupService", "GetGroupMemberInfo")
|
||||||
|
override suspend fun getGroupMemberInfo(request: GetGroupMemberInfoRequest): GetGroupMemberInfoResponse {
|
||||||
|
val memberInfo = GroupHelper.getTroopMemberInfoByUin(
|
||||||
|
request.groupId.toString(), when (request.targetCase!!) {
|
||||||
|
GetGroupMemberInfoRequest.TargetCase.TARGET_UID -> request.targetUin
|
||||||
|
GetGroupMemberInfoRequest.TargetCase.TARGET_UIN -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
|
||||||
|
else -> throw StatusRuntimeException(
|
||||||
|
Status.INVALID_ARGUMENT
|
||||||
|
.withDescription("target not set")
|
||||||
|
)
|
||||||
|
}.toString()
|
||||||
|
).onFailure {
|
||||||
|
throw StatusRuntimeException(
|
||||||
|
Status.INTERNAL.withDescription("unable to get group member info").withCause(it)
|
||||||
|
)
|
||||||
|
}.getOrThrow()
|
||||||
|
return GetGroupMemberInfoResponse.newBuilder().apply {
|
||||||
|
groupMemberInfo = GroupMemberInfo.newBuilder().apply {
|
||||||
|
uid =
|
||||||
|
if (request.targetCase == GetGroupMemberInfoRequest.TargetCase.TARGET_UID) request.targetUid else ContactHelper.getUidByUinAsync(
|
||||||
|
request.targetUin
|
||||||
|
)
|
||||||
|
uin = memberInfo.memberuin?.toLong() ?: 0
|
||||||
|
nick = memberInfo.troopnick
|
||||||
|
.ifNullOrEmpty { memberInfo.hwName }
|
||||||
|
.ifNullOrEmpty { memberInfo.troopColorNick }
|
||||||
|
.ifNullOrEmpty { memberInfo.friendnick } ?: ""
|
||||||
|
age = memberInfo.age.toInt()
|
||||||
|
uniqueTitle = memberInfo.mUniqueTitle ?: ""
|
||||||
|
uniqueTitleExpireTime = memberInfo.mUniqueTitleExpire
|
||||||
|
card = memberInfo.troopnick.ifNullOrEmpty { memberInfo.friendnick } ?: ""
|
||||||
|
joinTime = memberInfo.join_time
|
||||||
|
lastActiveTime = memberInfo.last_active_time
|
||||||
|
level = memberInfo.level
|
||||||
|
shutUpTimestamp = memberInfo.gagTimeStamp
|
||||||
|
|
||||||
|
distance = memberInfo.distance
|
||||||
|
addAllHonors((memberInfo.honorList ?: "")
|
||||||
|
.split("|")
|
||||||
|
.filter { it.isNotBlank() }
|
||||||
|
.map { it.toInt() })
|
||||||
|
unfriendly = false
|
||||||
|
cardChangeable = GroupHelper.isAdmin(request.groupId.toString())
|
||||||
|
}.build()
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("GroupService", "GetGroupMemberList")
|
||||||
|
override suspend fun getGroupMemberList(request: GetGroupMemberListRequest): GetGroupMemberListResponse {
|
||||||
|
val memberList = GroupHelper.getGroupMemberList(
|
||||||
|
request.groupId.toString(),
|
||||||
|
if (request.hasRefresh()) request.refresh else false
|
||||||
|
).onFailure {
|
||||||
|
throw StatusRuntimeException(
|
||||||
|
Status.INTERNAL.withDescription("unable to get group member list").withCause(it)
|
||||||
|
)
|
||||||
|
}.getOrThrow()
|
||||||
|
return GetGroupMemberListResponse.newBuilder().apply {
|
||||||
|
memberList.forEach { memberInfo ->
|
||||||
|
this.addGroupMembersInfo(GroupMemberInfo.newBuilder().apply {
|
||||||
|
uid = ContactHelper.getUidByUinAsync(memberInfo.memberuin?.toLong() ?: 0)
|
||||||
|
uin = memberInfo.memberuin?.toLong() ?: 0
|
||||||
|
nick = memberInfo.troopnick
|
||||||
|
.ifNullOrEmpty { memberInfo.hwName }
|
||||||
|
.ifNullOrEmpty { memberInfo.troopColorNick }
|
||||||
|
.ifNullOrEmpty { memberInfo.friendnick } ?: ""
|
||||||
|
age = memberInfo.age.toInt()
|
||||||
|
uniqueTitle = memberInfo.mUniqueTitle ?: ""
|
||||||
|
uniqueTitleExpireTime = memberInfo.mUniqueTitleExpire
|
||||||
|
card = memberInfo.troopnick.ifNullOrEmpty { memberInfo.friendnick } ?: ""
|
||||||
|
joinTime = memberInfo.join_time
|
||||||
|
lastActiveTime = memberInfo.last_active_time
|
||||||
|
level = memberInfo.level
|
||||||
|
shutUpTimestamp = memberInfo.gagTimeStamp
|
||||||
|
|
||||||
|
distance = memberInfo.distance
|
||||||
|
addAllHonors((memberInfo.honorList ?: "")
|
||||||
|
.split("|")
|
||||||
|
.filter { it.isNotBlank() }
|
||||||
|
.map { it.toInt() })
|
||||||
|
unfriendly = false
|
||||||
|
cardChangeable = GroupHelper.isAdmin(request.groupId.toString())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("GroupService", "GetProhibitedUserList")
|
||||||
|
override suspend fun getProhibitedUserList(request: GetProhibitedUserListRequest): GetProhibitedUserListResponse {
|
||||||
|
val prohibitedList = GroupHelper.getProhibitedMemberList(request.groupId).onFailure {
|
||||||
|
throw StatusRuntimeException(
|
||||||
|
Status.INTERNAL.withDescription("unable to get prohibited user list").withCause(it)
|
||||||
|
)
|
||||||
|
}.getOrThrow()
|
||||||
|
return GetProhibitedUserListResponse.newBuilder().apply {
|
||||||
|
prohibitedList.forEach {
|
||||||
|
this.addProhibitedUsersInfo(ProhibitedUserInfo.newBuilder().apply {
|
||||||
|
uid = ContactHelper.getUidByUinAsync(it.memberUin)
|
||||||
|
uin = it.memberUin
|
||||||
|
prohibitedTime = it.shutuptimestap
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("GroupService", "GetRemainCountAtAll")
|
||||||
|
override suspend fun getRemainCountAtAll(request: GetRemainCountAtAllRequest): GetRemainCountAtAllResponse {
|
||||||
|
val remainAtAllRsp = GroupHelper.getGroupRemainAtAllRemain(request.groupId).onFailure {
|
||||||
|
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get remain count").withCause(it))
|
||||||
|
}.getOrThrow()
|
||||||
|
return GetRemainCountAtAllResponse.newBuilder().apply {
|
||||||
|
accessAtAll = remainAtAllRsp.bool_can_at_all.get()
|
||||||
|
remainCountForGroup = remainAtAllRsp.uint32_remain_at_all_count_for_group.get()
|
||||||
|
remainCountForSelf = remainAtAllRsp.uint32_remain_at_all_count_for_uin.get()
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("GroupService", "GetNotJoinedGroupInfo")
|
||||||
|
override suspend fun getNotJoinedGroupInfo(request: GetNotJoinedGroupInfoRequest): GetNotJoinedGroupInfoResponse {
|
||||||
|
val groupInfo = GroupHelper.getNotJoinedGroupInfo(request.groupId).onFailure {
|
||||||
|
throw StatusRuntimeException(
|
||||||
|
Status.INTERNAL.withDescription("unable to get not joined group info").withCause(it)
|
||||||
|
)
|
||||||
|
}.getOrThrow()
|
||||||
|
return GetNotJoinedGroupInfoResponse.newBuilder().apply {
|
||||||
|
this.groupInfo = NotJoinedGroupInfo.newBuilder().apply {
|
||||||
|
groupId = groupInfo.groupId
|
||||||
|
groupName = groupInfo.groupName
|
||||||
|
owner = groupInfo.owner
|
||||||
|
maxMemberCount = groupInfo.maxMember
|
||||||
|
memberCount = groupInfo.memberCount
|
||||||
|
groupDesc = groupInfo.groupDesc
|
||||||
|
createTime = groupInfo.createTime.toInt()
|
||||||
|
groupFlag = groupInfo.groupFlag
|
||||||
|
groupFlagExt = groupInfo.groupFlagExt
|
||||||
|
}.build()
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("GroupService", "GetGroupHonor")
|
||||||
|
override suspend fun getGroupHonor(request: GetGroupHonorRequest): GetGroupHonorResponse {
|
||||||
|
return GetGroupHonorResponse.newBuilder().apply {
|
||||||
|
GroupHelper.getGroupMemberList(request.groupId.toString(), true).onFailure {
|
||||||
|
throw StatusRuntimeException(
|
||||||
|
Status.INTERNAL.withDescription("unable to get group member list").withCause(it)
|
||||||
|
)
|
||||||
|
}.onSuccess { memberList ->
|
||||||
|
memberList.forEach { member ->
|
||||||
|
(member.honorList ?: "").split("|")
|
||||||
|
.filter { it.isNotBlank() }
|
||||||
|
.map { it.toInt() }.forEach {
|
||||||
|
val honor = decodeHonor(member.memberuin.toLong(), it, member.mHonorRichFlag)
|
||||||
|
if (honor != null) {
|
||||||
|
addGroupHonorsInfo(GroupHonorInfo.newBuilder().apply {
|
||||||
|
uid = ContactHelper.getUidByUinAsync(member.memberuin.toLong())
|
||||||
|
uin = member.memberuin.toLong()
|
||||||
|
nick = member.troopnick
|
||||||
|
.ifNullOrEmpty { member.hwName }
|
||||||
|
.ifNullOrEmpty { member.troopColorNick }
|
||||||
|
.ifNullOrEmpty { member.friendnick } ?: ""
|
||||||
|
honorName = honor.honorName
|
||||||
|
avatar = honor.honorIconUrl
|
||||||
|
id = honor.honorId
|
||||||
|
description = honor.honorUrl
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
}
|
470
xposed/src/main/java/kritor/service/MessageService.kt
Normal file
470
xposed/src/main/java/kritor/service/MessageService.kt
Normal file
@ -0,0 +1,470 @@
|
|||||||
|
package kritor.service
|
||||||
|
|
||||||
|
import com.tencent.mobileqq.qroute.QRoute
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.Contact
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
|
||||||
|
import com.tencent.qqnt.msg.api.IMsgService
|
||||||
|
import io.grpc.Status
|
||||||
|
import io.grpc.StatusRuntimeException
|
||||||
|
import io.kritor.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() {
|
||||||
|
@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
|
||||||
|
this.messageId = detail.qqMsgId.toString()
|
||||||
|
this.messageSeq = detail.msgSeq
|
||||||
|
this.contact = io.kritor.common.Contact.newBuilder().apply {
|
||||||
|
this.scene = when (detail.msgType) {
|
||||||
|
MsgConstant.KCHATTYPEC2C -> Scene.FRIEND
|
||||||
|
MsgConstant.KCHATTYPEGROUP -> Scene.GROUP
|
||||||
|
MsgConstant.KCHATTYPEGUILD -> Scene.GUILD
|
||||||
|
MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> Scene.STRANGER_FROM_GROUP
|
||||||
|
MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN -> Scene.NEARBY
|
||||||
|
else -> Scene.STRANGER
|
||||||
|
}
|
||||||
|
this.peer = detail.peerId.toString()
|
||||||
|
}.build()
|
||||||
|
this.sender = Sender.newBuilder().apply {
|
||||||
|
this.uin = detail.sender.userId
|
||||||
|
this.nick = detail.sender.nickName
|
||||||
|
this.uid = detail.sender.uid
|
||||||
|
}.build()
|
||||||
|
detail.message?.elements?.toKritorResponseMessages(
|
||||||
|
com.tencent.qqnt.kernel.nativeinterface.Contact(
|
||||||
|
detail.msgType,
|
||||||
|
detail.peerId.toString(),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
)?.let {
|
||||||
|
this.addAllElements(it)
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Grpc("MessageService", "DeleteEssenceMessage")
|
||||||
|
override suspend fun deleteEssenceMessage(request: DeleteEssenceMessageRequest): DeleteEssenceMessageResponse {
|
||||||
|
val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, request.groupId.toString())
|
||||||
|
val msg: MsgRecord = withTimeoutOrNull(5000) {
|
||||||
|
val service = QRoute.api(IMsgService::class.java)
|
||||||
|
suspendCancellableCoroutine { continuation ->
|
||||||
|
service.getMsgsByMsgId(contact, arrayListOf(request.messageId.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.toInt()
|
||||||
|
this.senderNick = it.senderNick
|
||||||
|
this.senderUin = it.senderId
|
||||||
|
this.operationTime = it.operatorTime.toInt()
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
33
xposed/src/main/java/kritor/service/QsignService.kt
Normal file
33
xposed/src/main/java/kritor/service/QsignService.kt
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user