mirror of
https://github.com/whitechi73/OpenShamrock.git
synced 2024-08-14 05:12:17 +00:00
Compare commits
65 Commits
7bacea3288
...
master
Author | SHA1 | Date | |
---|---|---|---|
40ffa2b71b | |||
cca2cbbbd7 | |||
40d2911135 | |||
823d9911a0 | |||
4adbc12a0b | |||
114fbfdd23 | |||
0a563d60a1 | |||
5fb1d0aeb9 | |||
012ecaa85d | |||
7ea127a279 | |||
02f83a3c48 | |||
4016c05882 | |||
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 | |||
93c49953cf | |||
883e949cc1 |
3
.github/ISSUE_TEMPLATE/bug.md
vendored
3
.github/ISSUE_TEMPLATE/bug.md
vendored
@ -20,7 +20,8 @@ labels: bug
|
||||
|
||||
## 系统信息
|
||||
|
||||
- Shamrock 版本:
|
||||
- Shamrock 版本:
|
||||
- QQ 版本:
|
||||
- Android 版本:
|
||||
- LSPosed 框架版本:
|
||||
- 设备的制造商和型号:
|
||||
|
2
.github/workflows/build-apk.yml
vendored
2
.github/workflows/build-apk.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
|
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -1,3 +1,3 @@
|
||||
[submodule "kritor"]
|
||||
path = kritor
|
||||
path = kritor/kritor
|
||||
url = https://github.com/KarinJS/kritor
|
||||
|
27
README.md
27
README.md
@ -16,20 +16,39 @@
|
||||
|
||||
## 简介
|
||||
|
||||
☘ 基于 Lsposed(**Non**-Riru) 实现 OneBot 标准的 QQ 机器人框架,原作者[**fuqiuluo**](https://github.com/fuqiuluo)已脱离开发,接下来由白池接手哦!本项目为OpenShamrock,不会有任何收费行为,欢迎大家的加入!
|
||||
☘ 基于 Lsposed(**Non**-Riru) 实现 Kritor 标准的 QQ 机器人框架!
|
||||
|
||||
> 本项目仅提供学习与交流用途,请在24小时内删除。
|
||||
> 本项目目的是研究 Xposed 和 LSPosed 框架的使用。 Epic 框架开发相关知识。
|
||||
> Riru可能导致封禁,请减少使用。
|
||||
> 如有违反法律,请联系删除。
|
||||
> 请勿在任何平台宣传,宣扬,转发本项目,请勿恶意修改企业安装包造成相关企业产生损失,如有违背,必将追责到底。
|
||||
> 官方论坛,[点我直达](https://forum.libfekit.so/)!
|
||||
>
|
||||
> 社区地址:[discord](https://discord.gg/MKR2wz863h)
|
||||
|
||||
## 兼容|迁移|替代 说明
|
||||
|
||||
仅支持QQ9.0.70以上的版本,低版本问题将不再修复与处理。
|
||||
|
||||
- 一键移植:本项目基于 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>
|
||||
|
||||
## 权限声明
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
package kritor.service
|
||||
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
annotation class Grpc(
|
||||
val serviceName: String,
|
||||
val funcName: 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> = []
|
||||
)
|
@ -17,7 +17,7 @@ android {
|
||||
minSdk = 27
|
||||
targetSdk = 34
|
||||
versionCode = getVersionCode()
|
||||
versionName = "1.1.0" + ".r${getGitCommitCount()}." + getVersionName()
|
||||
versionName = "1.1.1" + ".r${getGitCommitCount()}." + getVersionName()
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
|
@ -37,7 +37,6 @@ add_library(${CMAKE_PROJECT_NAME} SHARED
|
||||
# List C/C++ source files with relative paths to this CMakeLists.txt.
|
||||
${SRC_DIR}
|
||||
md5.cpp
|
||||
cqcode.cpp
|
||||
silk.cpp
|
||||
message.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 "cqcode.h"
|
||||
#include <random>
|
||||
|
||||
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"
|
||||
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,
|
||||
jlong time) {
|
||||
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);
|
||||
}
|
||||
|
||||
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"
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_moe_fuqiuluo_shamrock_helper_MessageHelper_insertChatTypeToMsgId(JNIEnv *env, jobject thiz,
|
||||
|
@ -118,9 +118,7 @@ private fun APIInfoCard(
|
||||
text = rpcAddress,
|
||||
hint = "请输入回调地址",
|
||||
error = "输入的地址不合法",
|
||||
checker = {
|
||||
it.isEmpty() || it.contains(":")
|
||||
},
|
||||
checker = { true },
|
||||
confirm = {
|
||||
ShamrockConfig[ctx, RPCAddress] = rpcAddress.value
|
||||
AppRuntime.log("设置回调RPC地址为[${rpcAddress.value}]。")
|
||||
@ -164,7 +162,7 @@ private fun FunctionCard(
|
||||
|
||||
Function(
|
||||
title = "主动RPC",
|
||||
desc = "Kritor协议实现RPC",
|
||||
desc = "Kritor协议实现RPC,由Shamrock放出rpc服务",
|
||||
isSwitch = ShamrockConfig[ctx, ActiveRPC]
|
||||
) {
|
||||
ShamrockConfig[ctx, ActiveRPC] = it
|
||||
@ -173,7 +171,7 @@ private fun FunctionCard(
|
||||
|
||||
Function(
|
||||
title = "被动RPC",
|
||||
desc = "Kritor协议实现RPC",
|
||||
desc = "Kritor协议实现RPC,由客户端提供反向的rpc服务",
|
||||
isSwitch = ShamrockConfig[ctx, PassiveRPC]
|
||||
) {
|
||||
ShamrockConfig[ctx, PassiveRPC] = it
|
||||
|
@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
kotlin("jvm") version "1.9.21"
|
||||
kotlin("jvm") version "1.9.22"
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
@ -15,8 +15,9 @@ fun ktor(target: String, name: String): String {
|
||||
return "io.ktor:ktor-$target-$name:${Versions.ktorVersion}"
|
||||
}
|
||||
|
||||
fun grpc(name: String, version: String) = "io.grpc:grpc-$name:$version"
|
||||
|
||||
object Versions {
|
||||
const val roomVersion = "2.5.0"
|
||||
|
||||
const val ktorVersion = "2.3.3"
|
||||
}
|
1
kritor
1
kritor
Submodule kritor deleted from e4aac653e1
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 3dec747a8e
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
|
@ -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()
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package moe.fuqiuluo.ksp.providers
|
||||
|
||||
import com.google.auto.service.AutoService
|
||||
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
|
||||
import com.google.devtools.ksp.processing.SymbolProcessorProvider
|
||||
import moe.fuqiuluo.ksp.impl.GrpcProcessor
|
||||
|
||||
@AutoService(SymbolProcessorProvider::class)
|
||||
class GrpcProvider: SymbolProcessorProvider {
|
||||
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
|
||||
return GrpcProcessor(
|
||||
environment.codeGenerator,
|
||||
environment.logger
|
||||
)
|
||||
}
|
||||
}
|
@ -37,7 +37,6 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
//implementation(DEPENDENCY_PROTOBUF)
|
||||
implementation(kotlinx("serialization-protobuf", "1.6.2"))
|
||||
implementation(kotlinx("serialization-json", "1.6.2"))
|
||||
|
||||
|
@ -13,7 +13,7 @@ data class ButtonExtra(
|
||||
@Serializable
|
||||
data class Object1(
|
||||
@ProtoNumber(1) val rows: List<Row>? = null,
|
||||
@ProtoNumber(2) val appid: Int? = null,
|
||||
@ProtoNumber(2) val appid: ULong? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
@ -8,7 +8,9 @@ import moe.fuqiuluo.symbols.Protobuf
|
||||
data class TrpcOidb(
|
||||
@ProtoNumber(1) val cmd: Int = Int.MIN_VALUE,
|
||||
@ProtoNumber(2) val service: Int = Int.MIN_VALUE,
|
||||
@ProtoNumber(4) val buffer: ByteArray,
|
||||
@ProtoNumber(3) val result: UInt? = null,
|
||||
@ProtoNumber(4) val buffer: ByteArray? = null,
|
||||
@ProtoNumber(5) val msg: String? = null,
|
||||
//@ProtoNumber(11) val traceParams: Map<String, String> = mapOf(),
|
||||
@ProtoNumber(12) val flag: Int = Int.MIN_VALUE,
|
||||
): Protobuf<TrpcOidb>
|
@ -94,7 +94,8 @@ data class DeleteReq(
|
||||
|
||||
@Serializable
|
||||
data class DownloadRkeyReq(
|
||||
@ProtoNumber(1) val types: List<Int>
|
||||
@ProtoNumber(1) val types: List<Int>,
|
||||
@ProtoNumber(2) val downloadType: Int
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
@ -9,7 +9,7 @@ import moe.fuqiuluo.symbols.Protobuf
|
||||
|
||||
@Serializable
|
||||
data class NtV2RichMediaRsp(
|
||||
@ProtoNumber(1) val head: RspHead,
|
||||
@ProtoNumber(1) val head: RspHead?,
|
||||
@ProtoNumber(2) val upload: UploadRsp?,
|
||||
@ProtoNumber(3) val download: DownloadRsp?,
|
||||
@ProtoNumber(4) val downloadRkeyRsp: DownloadRkeyRsp?,
|
||||
@ -52,11 +52,11 @@ data class DownloadRkeyRsp(
|
||||
|
||||
@Serializable
|
||||
data class RKeyInfo(
|
||||
@ProtoNumber(1) val rkey: String?,
|
||||
@ProtoNumber(1) val rkey: String,
|
||||
@ProtoNumber(2) val rkeyTtlSec: ULong?,
|
||||
@ProtoNumber(3) val storeId: UInt = 0u,
|
||||
@ProtoNumber(4) val rkeyCreateTime: UInt?,
|
||||
@ProtoNumber(4) val type: UInt?,
|
||||
@ProtoNumber(4) val type: UInt,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
@ -1,7 +1,9 @@
|
||||
package com.tencent.qphone.base.util;
|
||||
|
||||
public abstract class CodecWarpper {
|
||||
public abstract void onResponse(int i2, Object obj, int i3);
|
||||
// public abstract void onResponse(int i2, Object obj, int i3);
|
||||
|
||||
public abstract void onInvalidData(int i2, int i3, String str);
|
||||
|
||||
public abstract void onResponse(int i2, Object obj, int i3, byte[] bArr);
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package com.tencent.qqnt.aio.api;
|
||||
|
||||
import com.tencent.mobileqq.qroute.QRouteApi;
|
||||
import com.tencent.qqnt.kernel.nativeinterface.Contact;
|
||||
import com.tencent.qqnt.kernelpublic.nativeinterface.Contact;
|
||||
import com.tencent.qqnt.kernel.nativeinterface.IOperateCallback;
|
||||
|
||||
public interface IAIOFileTransfer extends QRouteApi {
|
||||
|
@ -0,0 +1,4 @@
|
||||
package com.tencent.qqnt.kernel.api.impl;
|
||||
|
||||
public class GroupService {
|
||||
}
|
@ -18,6 +18,11 @@ public class MsgService {
|
||||
public void addMsgListener(IKernelMsgListener listener) {
|
||||
}
|
||||
|
||||
public void removeMsgListener(@NotNull IKernelMsgListener iKernelMsgListener) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
public String getRichMediaFilePathForGuild(@NotNull RichMediaFilePathInfo richMediaFilePathInfo) {
|
||||
return null;
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
|
||||
import com.tencent.qqnt.kernelpublic.nativeinterface.Contact;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
|
@ -0,0 +1,4 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
|
||||
public class GProGuildTopFeedMsg {
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
|
||||
import com.tencent.qqnt.kernelpublic.nativeinterface.Contact;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
|
@ -0,0 +1,36 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class GroupMemberCommonListResult {
|
||||
public long groupCode;
|
||||
public int identifyFlag;
|
||||
public long startUin;
|
||||
public ArrayList<MemberCommonInfo> memberList = new ArrayList<>();
|
||||
public String strErrorInfo = "";
|
||||
|
||||
public long getGroupCode() {
|
||||
return this.groupCode;
|
||||
}
|
||||
|
||||
public int getIdentifyFlag() {
|
||||
return this.identifyFlag;
|
||||
}
|
||||
|
||||
public ArrayList<MemberCommonInfo> getMemberList() {
|
||||
return this.memberList;
|
||||
}
|
||||
|
||||
public long getStartUin() {
|
||||
return this.startUin;
|
||||
}
|
||||
|
||||
public String getStrErrorInfo() {
|
||||
return this.strErrorInfo;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "GroupMemberCommonListResult{groupCode=" + this.groupCode + ",startUin=" + this.startUin + ",identifyFlag=" + this.identifyFlag + ",memberList=" + this.memberList + ",strErrorInfo=" + this.strErrorInfo + ",}";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class GroupMemberCommonReq {
|
||||
public long groupCode;
|
||||
public int sourceType;
|
||||
public String startUin = "";
|
||||
public String identifyFlag = "";
|
||||
public ArrayList<Long> uinList = new ArrayList<>();
|
||||
public MemberCommonInfoFilter memberCommonFilter = new MemberCommonInfoFilter();
|
||||
public String memberNum = "";
|
||||
public String filterMethod = "";
|
||||
public String onlineFlag = "";
|
||||
public String realSpecialTitleFlag = "";
|
||||
|
||||
public String getFilterMethod() {
|
||||
return this.filterMethod;
|
||||
}
|
||||
|
||||
public long getGroupCode() {
|
||||
return this.groupCode;
|
||||
}
|
||||
|
||||
public String getIdentifyFlag() {
|
||||
return this.identifyFlag;
|
||||
}
|
||||
|
||||
public MemberCommonInfoFilter getMemberCommonFilter() {
|
||||
return this.memberCommonFilter;
|
||||
}
|
||||
|
||||
public String getMemberNum() {
|
||||
return this.memberNum;
|
||||
}
|
||||
|
||||
public String getOnlineFlag() {
|
||||
return this.onlineFlag;
|
||||
}
|
||||
|
||||
public String getRealSpecialTitleFlag() {
|
||||
return this.realSpecialTitleFlag;
|
||||
}
|
||||
|
||||
public int getSourceType() {
|
||||
return this.sourceType;
|
||||
}
|
||||
|
||||
public String getStartUin() {
|
||||
return this.startUin;
|
||||
}
|
||||
|
||||
public ArrayList<Long> getUinList() {
|
||||
return this.uinList;
|
||||
}
|
||||
|
||||
public void setFilterMethod(String str) {
|
||||
this.filterMethod = str;
|
||||
}
|
||||
|
||||
public void setGroupCode(long j2) {
|
||||
this.groupCode = j2;
|
||||
}
|
||||
|
||||
public void setIdentifyFlag(String str) {
|
||||
this.identifyFlag = str;
|
||||
}
|
||||
|
||||
public void setMemberCommonFilter(MemberCommonInfoFilter memberCommonInfoFilter) {
|
||||
this.memberCommonFilter = memberCommonInfoFilter;
|
||||
}
|
||||
|
||||
public void setMemberNum(String str) {
|
||||
this.memberNum = str;
|
||||
}
|
||||
|
||||
public void setOnlineFlag(String str) {
|
||||
this.onlineFlag = str;
|
||||
}
|
||||
|
||||
public void setRealSpecialTitleFlag(String str) {
|
||||
this.realSpecialTitleFlag = str;
|
||||
}
|
||||
|
||||
public void setSourceType(int i2) {
|
||||
this.sourceType = i2;
|
||||
}
|
||||
|
||||
public void setStartUin(String str) {
|
||||
this.startUin = str;
|
||||
}
|
||||
|
||||
public void setUinList(ArrayList<Long> arrayList) {
|
||||
this.uinList = arrayList;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "GroupMemberCommonReq{groupCode=" + this.groupCode + ",startUin=" + this.startUin + ",identifyFlag=" + this.identifyFlag + ",uinList=" + this.uinList + ",memberCommonFilter=" + this.memberCommonFilter + ",memberNum=" + this.memberNum + ",filterMethod=" + this.filterMethod + ",onlineFlag=" + this.onlineFlag + ",realSpecialTitleFlag=" + this.realSpecialTitleFlag + ",sourceType=" + this.sourceType + ",}";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class GroupMemberExtListResult {
|
||||
public long dataTime;
|
||||
public long endUin;
|
||||
public long groupCode;
|
||||
public int levelNameSeq;
|
||||
public int memberInfoSeq;
|
||||
public int sysShowFlag;
|
||||
public int timeToUpdate;
|
||||
public int userShowFlag;
|
||||
public int userShowFlagNew;
|
||||
public ArrayList<MemberExtInfo> memberLevelInfo = new ArrayList<>();
|
||||
public ArrayList<MemberLevelName> msgLevelName = new ArrayList<>();
|
||||
public String strOwnerName = "";
|
||||
public String strAdminName = "";
|
||||
public ArrayList<MemberLevelName> msgLevelNameNew = new ArrayList<>();
|
||||
|
||||
public long getDataTime() {
|
||||
return this.dataTime;
|
||||
}
|
||||
|
||||
public long getEndUin() {
|
||||
return this.endUin;
|
||||
}
|
||||
|
||||
public long getGroupCode() {
|
||||
return this.groupCode;
|
||||
}
|
||||
|
||||
public int getLevelNameSeq() {
|
||||
return this.levelNameSeq;
|
||||
}
|
||||
|
||||
public int getMemberInfoSeq() {
|
||||
return this.memberInfoSeq;
|
||||
}
|
||||
|
||||
public ArrayList<MemberExtInfo> getMemberLevelInfo() {
|
||||
return this.memberLevelInfo;
|
||||
}
|
||||
|
||||
public ArrayList<MemberLevelName> getMsgLevelName() {
|
||||
return this.msgLevelName;
|
||||
}
|
||||
|
||||
public ArrayList<MemberLevelName> getMsgLevelNameNew() {
|
||||
return this.msgLevelNameNew;
|
||||
}
|
||||
|
||||
public String getStrAdminName() {
|
||||
return this.strAdminName;
|
||||
}
|
||||
|
||||
public String getStrOwnerName() {
|
||||
return this.strOwnerName;
|
||||
}
|
||||
|
||||
public int getSysShowFlag() {
|
||||
return this.sysShowFlag;
|
||||
}
|
||||
|
||||
public int getTimeToUpdate() {
|
||||
return this.timeToUpdate;
|
||||
}
|
||||
|
||||
public int getUserShowFlag() {
|
||||
return this.userShowFlag;
|
||||
}
|
||||
|
||||
public int getUserShowFlagNew() {
|
||||
return this.userShowFlagNew;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "GroupMemberExtListResult{groupCode=" + this.groupCode + ",memberLevelInfo=" + this.memberLevelInfo + ",msgLevelName=" + this.msgLevelName + ",endUin=" + this.endUin + ",dataTime=" + this.dataTime + ",userShowFlag=" + this.userShowFlag + ",sysShowFlag=" + this.sysShowFlag + ",timeToUpdate=" + this.timeToUpdate + ",strOwnerName=" + this.strOwnerName + ",strAdminName=" + this.strAdminName + ",levelNameSeq=" + this.levelNameSeq + ",userShowFlagNew=" + this.userShowFlagNew + ",msgLevelNameNew=" + this.msgLevelNameNew + ",memberInfoSeq=" + this.memberInfoSeq + ",}";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class GroupMemberExtReq {
|
||||
public long groupCode;
|
||||
public int sourceType;
|
||||
public String beginUin = "";
|
||||
public String dataTime = "";
|
||||
public ArrayList<Long> uinList = new ArrayList<>();
|
||||
public MemberExtInfoFilter memberExtFilter = new MemberExtInfoFilter();
|
||||
public String seq = "";
|
||||
public String uinNum = "";
|
||||
public String groupType = "";
|
||||
public String richCardNameVer = "";
|
||||
|
||||
public String getBeginUin() {
|
||||
return this.beginUin;
|
||||
}
|
||||
|
||||
public String getDataTime() {
|
||||
return this.dataTime;
|
||||
}
|
||||
|
||||
public long getGroupCode() {
|
||||
return this.groupCode;
|
||||
}
|
||||
|
||||
public String getGroupType() {
|
||||
return this.groupType;
|
||||
}
|
||||
|
||||
public MemberExtInfoFilter getMemberExtFilter() {
|
||||
return this.memberExtFilter;
|
||||
}
|
||||
|
||||
public String getRichCardNameVer() {
|
||||
return this.richCardNameVer;
|
||||
}
|
||||
|
||||
public String getSeq() {
|
||||
return this.seq;
|
||||
}
|
||||
|
||||
public int getSourceType() {
|
||||
return this.sourceType;
|
||||
}
|
||||
|
||||
public ArrayList<Long> getUinList() {
|
||||
return this.uinList;
|
||||
}
|
||||
|
||||
public String getUinNum() {
|
||||
return this.uinNum;
|
||||
}
|
||||
|
||||
public void setBeginUin(String str) {
|
||||
this.beginUin = str;
|
||||
}
|
||||
|
||||
public void setDataTime(String str) {
|
||||
this.dataTime = str;
|
||||
}
|
||||
|
||||
public void setGroupCode(long j2) {
|
||||
this.groupCode = j2;
|
||||
}
|
||||
|
||||
public void setGroupType(String str) {
|
||||
this.groupType = str;
|
||||
}
|
||||
|
||||
public void setMemberExtFilter(MemberExtInfoFilter memberExtInfoFilter) {
|
||||
this.memberExtFilter = memberExtInfoFilter;
|
||||
}
|
||||
|
||||
public void setRichCardNameVer(String str) {
|
||||
this.richCardNameVer = str;
|
||||
}
|
||||
|
||||
public void setSeq(String str) {
|
||||
this.seq = str;
|
||||
}
|
||||
|
||||
public void setSourceType(int i2) {
|
||||
this.sourceType = i2;
|
||||
}
|
||||
|
||||
public void setUinList(ArrayList<Long> arrayList) {
|
||||
this.uinList = arrayList;
|
||||
}
|
||||
|
||||
public void setUinNum(String str) {
|
||||
this.uinNum = str;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "GroupMemberExtReq{groupCode=" + this.groupCode + ",beginUin=" + this.beginUin + ",dataTime=" + this.dataTime + ",uinList=" + this.uinList + ",memberExtFilter=" + this.memberExtFilter + ",seq=" + this.seq + ",uinNum=" + this.uinNum + ",groupType=" + this.groupType + ",richCardNameVer=" + this.richCardNameVer + ",sourceType=" + this.sourceType + ",}";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class GroupMemberListResult {
|
||||
public boolean finish;
|
||||
public boolean hasRobot;
|
||||
public ArrayList<GroupMemberInfoListId> ids = new ArrayList<>();
|
||||
public HashMap<String, MemberInfo> infos = new HashMap<>();
|
||||
|
||||
public boolean getFinish() {
|
||||
return this.finish;
|
||||
}
|
||||
|
||||
public boolean getHasRobot() {
|
||||
return this.hasRobot;
|
||||
}
|
||||
|
||||
public ArrayList<GroupMemberInfoListId> getIds() {
|
||||
return this.ids;
|
||||
}
|
||||
|
||||
public HashMap<String, MemberInfo> getInfos() {
|
||||
return this.infos;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "GroupMemberListResult{ids=" + this.ids + ",infos=" + this.infos + ",finish=" + this.finish + ",hasRobot=" + this.hasRobot + ",}";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
|
||||
public interface IGroupMemberCommonCallback {
|
||||
void onResult(int i2, String str, GroupMemberCommonListResult groupMemberCommonListResult);
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
|
||||
public interface IGroupMemberExtCallback {
|
||||
void onResult(int i2, String str, GroupMemberExtListResult groupMemberExtListResult);
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
|
||||
public interface IGroupMemberListCallback {
|
||||
void onResult(int result, String str, GroupMemberListResult groupMemberListResult);
|
||||
|
||||
}
|
@ -1,7 +1,25 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public interface IKernelGroupService {
|
||||
void getTransferableMemberInfo(long uin, IGetTransferableMemberCallback cb);
|
||||
|
||||
long addKernelGroupListener(IKernelGroupListener ln);
|
||||
|
||||
void getAllMemberList(long groupCode, boolean refresh, IGroupMemberListCallback iGroupMemberListCallback);
|
||||
|
||||
void getMemberCommonInfo(GroupMemberCommonReq groupMemberCommonReq, IGroupMemberCommonCallback iGroupMemberCommonCallback);
|
||||
|
||||
void getMemberExtInfo(GroupMemberExtReq groupMemberExtReq, IGroupMemberExtCallback iGroupMemberExtCallback);
|
||||
|
||||
void getMemberInfo(long j2, ArrayList<String> arrayList, boolean z, IOperateCallback iOperateCallback);
|
||||
|
||||
void getMemberInfoForMqq(long groupCode, ArrayList<String> uids, boolean z, IGroupMemberListCallback iGroupMemberListCallback);
|
||||
|
||||
void getNextMemberList(String sceneId, GroupMemberInfoListId groupMemberInfoListId, int i2, IGroupMemberListCallback iGroupMemberListCallback);
|
||||
|
||||
void getPrevMemberList(String sceneId, GroupMemberInfoListId groupMemberInfoListId, int i2, IGroupMemberListCallback iGroupMemberListCallback);
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
|
||||
import com.tencent.qqnt.kernelpublic.nativeinterface.Contact;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
@ -16,7 +18,7 @@ public interface IKernelMsgListener {
|
||||
|
||||
void onCustomWithdrawConfigUpdate(CustomWithdrawConfig customWithdrawConfig);
|
||||
|
||||
void onDraftUpdate(Contact contact, ArrayList<MsgElement> arrayList, long j2);
|
||||
void onDraftUpdate(Contact contact, ArrayList<MsgElement> arrayList, long j);
|
||||
|
||||
void onEmojiDownloadComplete(EmojiNotifyInfo emojiNotifyInfo);
|
||||
|
||||
@ -30,7 +32,7 @@ public interface IKernelMsgListener {
|
||||
|
||||
void onFirstViewGroupGuildMapping(ArrayList<FirstViewGroupGuildInfo> arrayList);
|
||||
|
||||
void onGrabPasswordRedBag(int i2, String str, int i3, RecvdOrder recvdOrder, MsgRecord msgRecord);
|
||||
void onGrabPasswordRedBag(int i, String str, int i2, RecvdOrder recvdOrder, MsgRecord msgRecord);
|
||||
|
||||
void onGroupFileInfoAdd(GroupItem groupItem);
|
||||
|
||||
@ -48,6 +50,8 @@ public interface IKernelMsgListener {
|
||||
|
||||
void onGuildNotificationAbstractUpdate(GuildNotificationAbstractInfo guildNotificationAbstractInfo);
|
||||
|
||||
void onGuildTopFeedUpdate(GProGuildTopFeedMsg gProGuildTopFeedMsg);
|
||||
|
||||
void onHitCsRelatedEmojiResult(DownloadRelateEmojiResultInfo downloadRelateEmojiResultInfo);
|
||||
|
||||
void onHitEmojiKeywordResult(HitRelatedEmojiWordsResult hitRelatedEmojiWordsResult);
|
||||
@ -62,7 +66,7 @@ public interface IKernelMsgListener {
|
||||
|
||||
void onLineDev(ArrayList<DevInfo> arrayList);
|
||||
|
||||
void onLogLevelChanged(long j2);
|
||||
void onLogLevelChanged(long j);
|
||||
|
||||
void onMsgAbstractUpdate(ArrayList<MsgAbstract> arrayList);
|
||||
|
||||
@ -76,14 +80,16 @@ public interface IKernelMsgListener {
|
||||
|
||||
void onMsgInfoListUpdate(ArrayList<MsgRecord> arrayList);
|
||||
|
||||
void onMsgQRCodeStatusChanged(int i2);
|
||||
void onMsgQRCodeStatusChanged(int i);
|
||||
|
||||
void onMsgRecall(int i2, String str, long j2);
|
||||
void onMsgRecall(int i, String str, long j);
|
||||
|
||||
void onMsgSecurityNotify(MsgRecord msgRecord);
|
||||
|
||||
void onMsgSettingUpdate(MsgSetting msgSetting);
|
||||
|
||||
void onMsgWithRichLinkInfoUpdate(ArrayList<MsgRecord> arrayList);
|
||||
|
||||
void onNtFirstViewMsgSyncEnd();
|
||||
|
||||
void onNtMsgSyncEnd();
|
||||
@ -92,11 +98,11 @@ public interface IKernelMsgListener {
|
||||
|
||||
void onReadFeedEventUpdate(FirstViewDirectMsgNotifyInfo firstViewDirectMsgNotifyInfo);
|
||||
|
||||
void onRecvGroupGuildFlag(int i2);
|
||||
void onRecvGroupGuildFlag(int i);
|
||||
|
||||
void onRecvMsg(ArrayList<MsgRecord> arrayList);
|
||||
|
||||
void onRecvMsgSvrRspTransInfo(long j2, Contact contact, int i2, int i3, String str, byte[] bArr);
|
||||
void onRecvMsgSvrRspTransInfo(long j, Contact contact, int i, int i2, String str, byte[] bArr);
|
||||
|
||||
void onRecvOnlineFileMsg(ArrayList<MsgRecord> arrayList);
|
||||
|
||||
@ -104,7 +110,9 @@ public interface IKernelMsgListener {
|
||||
|
||||
void onRecvSysMsg(ArrayList<Byte> arrayList);
|
||||
|
||||
void onRecvUDCFlag(int i2);
|
||||
void onRecvUDCFlag(int i);
|
||||
|
||||
void onRedTouchChanged();
|
||||
|
||||
void onRichMediaDownloadComplete(FileTransNotifyInfo fileTransNotifyInfo);
|
||||
|
||||
@ -114,9 +122,9 @@ public interface IKernelMsgListener {
|
||||
|
||||
void onSearchGroupFileInfoUpdate(SearchGroupFileResult searchGroupFileResult);
|
||||
|
||||
void onSendMsgError(long j2, Contact contact, int i2, String str);
|
||||
void onSendMsgError(long j, Contact contact, int i, String str);
|
||||
|
||||
void onSysMsgNotification(int i2, long j2, long j3, ArrayList<Byte> arrayList);
|
||||
void onSysMsgNotification(int i, long j, long j2, boolean z, ArrayList<Byte> arrayList);
|
||||
|
||||
void onTempChatInfoUpdate(TempChatInfo tempChatInfo);
|
||||
|
||||
@ -128,9 +136,11 @@ public interface IKernelMsgListener {
|
||||
|
||||
void onUserOnlineStatusChanged(boolean z);
|
||||
|
||||
void onUserSecQualityChanged(QueryUserSecQualityRsp queryUserSecQualityRsp);
|
||||
|
||||
void onUserTabStatusChanged(ArrayList<TabStatusInfo> arrayList);
|
||||
|
||||
void onlineStatusBigIconDownloadPush(int i2, long j2, String str);
|
||||
void onlineStatusBigIconDownloadPush(int i, long j, String str);
|
||||
|
||||
void onlineStatusSmallIconDownloadPush(int i2, long j2, String str);
|
||||
void onlineStatusSmallIconDownloadPush(int i, long j, String str);
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
|
||||
import com.tencent.qqnt.kernelpublic.nativeinterface.Contact;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -1,5 +1,7 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
|
||||
import com.tencent.qqnt.kernelpublic.nativeinterface.Contact;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public interface IKernelRichMediaService {
|
||||
|
@ -0,0 +1,63 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
|
||||
public class MemberCommonInfo {
|
||||
public long memberUin;
|
||||
public int privilege;
|
||||
public int shutUpTime;
|
||||
public int uinFlag;
|
||||
public int uinFlagExt;
|
||||
public int uinMobileFlag;
|
||||
|
||||
public long getMemberUin() {
|
||||
return this.memberUin;
|
||||
}
|
||||
|
||||
public int getPrivilege() {
|
||||
return this.privilege;
|
||||
}
|
||||
|
||||
public int getShutUpTime() {
|
||||
return this.shutUpTime;
|
||||
}
|
||||
|
||||
public int getUinFlag() {
|
||||
return this.uinFlag;
|
||||
}
|
||||
|
||||
public int getUinFlagExt() {
|
||||
return this.uinFlagExt;
|
||||
}
|
||||
|
||||
public int getUinMobileFlag() {
|
||||
return this.uinMobileFlag;
|
||||
}
|
||||
|
||||
public void setMemberUin(long j2) {
|
||||
this.memberUin = j2;
|
||||
}
|
||||
|
||||
public void setPrivilege(int i2) {
|
||||
this.privilege = i2;
|
||||
}
|
||||
|
||||
public void setShutUpTime(int i2) {
|
||||
this.shutUpTime = i2;
|
||||
}
|
||||
|
||||
public void setUinFlag(int i2) {
|
||||
this.uinFlag = i2;
|
||||
}
|
||||
|
||||
public void setUinFlagExt(int i2) {
|
||||
this.uinFlagExt = i2;
|
||||
}
|
||||
|
||||
public void setUinMobileFlag(int i2) {
|
||||
this.uinMobileFlag = i2;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "MemberCommonInfo{memberUin=" + this.memberUin + ",uinFlag=" + this.uinFlag + ",uinFlagExt=" + this.uinFlagExt + ",uinMobileFlag=" + this.uinMobileFlag + ",shutUpTime=" + this.shutUpTime + ",privilege=" + this.privilege + ",}";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
|
||||
public class MemberCommonInfoFilter {
|
||||
public int memberUin;
|
||||
public int privilege;
|
||||
public int shutUpTime;
|
||||
public int uinFlag;
|
||||
public int uinFlagExt;
|
||||
public int uinMobileFlag;
|
||||
|
||||
public int getMemberUin() {
|
||||
return this.memberUin;
|
||||
}
|
||||
|
||||
public int getPrivilege() {
|
||||
return this.privilege;
|
||||
}
|
||||
|
||||
public int getShutUpTime() {
|
||||
return this.shutUpTime;
|
||||
}
|
||||
|
||||
public int getUinFlag() {
|
||||
return this.uinFlag;
|
||||
}
|
||||
|
||||
public int getUinFlagExt() {
|
||||
return this.uinFlagExt;
|
||||
}
|
||||
|
||||
public int getUinMobileFlag() {
|
||||
return this.uinMobileFlag;
|
||||
}
|
||||
|
||||
public void setMemberUin(int i2) {
|
||||
this.memberUin = i2;
|
||||
}
|
||||
|
||||
public void setPrivilege(int i2) {
|
||||
this.privilege = i2;
|
||||
}
|
||||
|
||||
public void setShutUpTime(int i2) {
|
||||
this.shutUpTime = i2;
|
||||
}
|
||||
|
||||
public void setUinFlag(int i2) {
|
||||
this.uinFlag = i2;
|
||||
}
|
||||
|
||||
public void setUinFlagExt(int i2) {
|
||||
this.uinFlagExt = i2;
|
||||
}
|
||||
|
||||
public void setUinMobileFlag(int i2) {
|
||||
this.uinMobileFlag = i2;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "MemberCommonInfoFilter{memberUin=" + this.memberUin + ",uinFlag=" + this.uinFlag + ",uinFlagExt=" + this.uinFlagExt + ",uinMobileFlag=" + this.uinMobileFlag + ",shutUpTime=" + this.shutUpTime + ",privilege=" + this.privilege + ",}";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class MemberExtInfo {
|
||||
public int activeDay;
|
||||
public int cmdUinFlagExt3Grocery;
|
||||
public int level;
|
||||
public int point;
|
||||
public long specialTitleExpireTime;
|
||||
public long uin;
|
||||
public String strName = "";
|
||||
public String nickName = "";
|
||||
public String specialTitle = "";
|
||||
public byte[] msgNeedField = new byte[0];
|
||||
public ArrayList<MemberIcon> memberIconList = new ArrayList<>();
|
||||
|
||||
public int getActiveDay() {
|
||||
return this.activeDay;
|
||||
}
|
||||
|
||||
public int getCmdUinFlagExt3Grocery() {
|
||||
return this.cmdUinFlagExt3Grocery;
|
||||
}
|
||||
|
||||
public int getLevel() {
|
||||
return this.level;
|
||||
}
|
||||
|
||||
public ArrayList<MemberIcon> getMemberIconList() {
|
||||
return this.memberIconList;
|
||||
}
|
||||
|
||||
public byte[] getMsgNeedField() {
|
||||
return this.msgNeedField;
|
||||
}
|
||||
|
||||
public String getNickName() {
|
||||
return this.nickName;
|
||||
}
|
||||
|
||||
public int getPoint() {
|
||||
return this.point;
|
||||
}
|
||||
|
||||
public String getSpecialTitle() {
|
||||
return this.specialTitle;
|
||||
}
|
||||
|
||||
public long getSpecialTitleExpireTime() {
|
||||
return this.specialTitleExpireTime;
|
||||
}
|
||||
|
||||
public String getStrName() {
|
||||
return this.strName;
|
||||
}
|
||||
|
||||
public long getUin() {
|
||||
return this.uin;
|
||||
}
|
||||
|
||||
public void setActiveDay(int i2) {
|
||||
this.activeDay = i2;
|
||||
}
|
||||
|
||||
public void setCmdUinFlagExt3Grocery(int i2) {
|
||||
this.cmdUinFlagExt3Grocery = i2;
|
||||
}
|
||||
|
||||
public void setLevel(int i2) {
|
||||
this.level = i2;
|
||||
}
|
||||
|
||||
public void setMemberIconList(ArrayList<MemberIcon> arrayList) {
|
||||
this.memberIconList = arrayList;
|
||||
}
|
||||
|
||||
public void setMsgNeedField(byte[] bArr) {
|
||||
this.msgNeedField = bArr;
|
||||
}
|
||||
|
||||
public void setNickName(String str) {
|
||||
this.nickName = str;
|
||||
}
|
||||
|
||||
public void setPoint(int i2) {
|
||||
this.point = i2;
|
||||
}
|
||||
|
||||
public void setSpecialTitle(String str) {
|
||||
this.specialTitle = str;
|
||||
}
|
||||
|
||||
public void setSpecialTitleExpireTime(long j2) {
|
||||
this.specialTitleExpireTime = j2;
|
||||
}
|
||||
|
||||
public void setStrName(String str) {
|
||||
this.strName = str;
|
||||
}
|
||||
|
||||
public void setUin(long j2) {
|
||||
this.uin = j2;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "MemberExtInfo{uin=" + this.uin + ",point=" + this.point + ",activeDay=" + this.activeDay + ",level=" + this.level + ",strName=" + this.strName + ",nickName=" + this.nickName + ",specialTitle=" + this.specialTitle + ",specialTitleExpireTime=" + this.specialTitleExpireTime + ",msgNeedField=" + this.msgNeedField + ",cmdUinFlagExt3Grocery=" + this.cmdUinFlagExt3Grocery + ",memberIconList=" + this.memberIconList + ",}";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,171 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
|
||||
public class MemberExtInfoFilter {
|
||||
public int cmdUinFlagExt3Grocery;
|
||||
public int dataTime;
|
||||
public int levelName;
|
||||
public int levelNameNew;
|
||||
public int memberIcon;
|
||||
public int memberInfoSeq;
|
||||
public int memberLevelInfoActiveDay;
|
||||
public int memberLevelInfoLevel;
|
||||
public int memberLevelInfoName;
|
||||
public int memberLevelInfoPoint;
|
||||
public int memberLevelInfoUin;
|
||||
public int msgNeedField;
|
||||
public int nickName;
|
||||
public int specialTitle;
|
||||
public int sysShowFlag;
|
||||
public int timeToUpdate;
|
||||
public int userShowFlag;
|
||||
public int userShowFlagNew;
|
||||
|
||||
public int getCmdUinFlagExt3Grocery() {
|
||||
return this.cmdUinFlagExt3Grocery;
|
||||
}
|
||||
|
||||
public int getDataTime() {
|
||||
return this.dataTime;
|
||||
}
|
||||
|
||||
public int getLevelName() {
|
||||
return this.levelName;
|
||||
}
|
||||
|
||||
public int getLevelNameNew() {
|
||||
return this.levelNameNew;
|
||||
}
|
||||
|
||||
public int getMemberIcon() {
|
||||
return this.memberIcon;
|
||||
}
|
||||
|
||||
public int getMemberInfoSeq() {
|
||||
return this.memberInfoSeq;
|
||||
}
|
||||
|
||||
public int getMemberLevelInfoActiveDay() {
|
||||
return this.memberLevelInfoActiveDay;
|
||||
}
|
||||
|
||||
public int getMemberLevelInfoLevel() {
|
||||
return this.memberLevelInfoLevel;
|
||||
}
|
||||
|
||||
public int getMemberLevelInfoName() {
|
||||
return this.memberLevelInfoName;
|
||||
}
|
||||
|
||||
public int getMemberLevelInfoPoint() {
|
||||
return this.memberLevelInfoPoint;
|
||||
}
|
||||
|
||||
public int getMemberLevelInfoUin() {
|
||||
return this.memberLevelInfoUin;
|
||||
}
|
||||
|
||||
public int getMsgNeedField() {
|
||||
return this.msgNeedField;
|
||||
}
|
||||
|
||||
public int getNickName() {
|
||||
return this.nickName;
|
||||
}
|
||||
|
||||
public int getSpecialTitle() {
|
||||
return this.specialTitle;
|
||||
}
|
||||
|
||||
public int getSysShowFlag() {
|
||||
return this.sysShowFlag;
|
||||
}
|
||||
|
||||
public int getTimeToUpdate() {
|
||||
return this.timeToUpdate;
|
||||
}
|
||||
|
||||
public int getUserShowFlag() {
|
||||
return this.userShowFlag;
|
||||
}
|
||||
|
||||
public int getUserShowFlagNew() {
|
||||
return this.userShowFlagNew;
|
||||
}
|
||||
|
||||
public void setCmdUinFlagExt3Grocery(int i2) {
|
||||
this.cmdUinFlagExt3Grocery = i2;
|
||||
}
|
||||
|
||||
public void setDataTime(int i2) {
|
||||
this.dataTime = i2;
|
||||
}
|
||||
|
||||
public void setLevelName(int i2) {
|
||||
this.levelName = i2;
|
||||
}
|
||||
|
||||
public void setLevelNameNew(int i2) {
|
||||
this.levelNameNew = i2;
|
||||
}
|
||||
|
||||
public void setMemberIcon(int i2) {
|
||||
this.memberIcon = i2;
|
||||
}
|
||||
|
||||
public void setMemberInfoSeq(int i2) {
|
||||
this.memberInfoSeq = i2;
|
||||
}
|
||||
|
||||
public void setMemberLevelInfoActiveDay(int i2) {
|
||||
this.memberLevelInfoActiveDay = i2;
|
||||
}
|
||||
|
||||
public void setMemberLevelInfoLevel(int i2) {
|
||||
this.memberLevelInfoLevel = i2;
|
||||
}
|
||||
|
||||
public void setMemberLevelInfoName(int i2) {
|
||||
this.memberLevelInfoName = i2;
|
||||
}
|
||||
|
||||
public void setMemberLevelInfoPoint(int i2) {
|
||||
this.memberLevelInfoPoint = i2;
|
||||
}
|
||||
|
||||
public void setMemberLevelInfoUin(int i2) {
|
||||
this.memberLevelInfoUin = i2;
|
||||
}
|
||||
|
||||
public void setMsgNeedField(int i2) {
|
||||
this.msgNeedField = i2;
|
||||
}
|
||||
|
||||
public void setNickName(int i2) {
|
||||
this.nickName = i2;
|
||||
}
|
||||
|
||||
public void setSpecialTitle(int i2) {
|
||||
this.specialTitle = i2;
|
||||
}
|
||||
|
||||
public void setSysShowFlag(int i2) {
|
||||
this.sysShowFlag = i2;
|
||||
}
|
||||
|
||||
public void setTimeToUpdate(int i2) {
|
||||
this.timeToUpdate = i2;
|
||||
}
|
||||
|
||||
public void setUserShowFlag(int i2) {
|
||||
this.userShowFlag = i2;
|
||||
}
|
||||
|
||||
public void setUserShowFlagNew(int i2) {
|
||||
this.userShowFlagNew = i2;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "MemberExtInfoFilter{memberLevelInfoUin=" + this.memberLevelInfoUin + ",memberLevelInfoPoint=" + this.memberLevelInfoPoint + ",memberLevelInfoActiveDay=" + this.memberLevelInfoActiveDay + ",memberLevelInfoLevel=" + this.memberLevelInfoLevel + ",memberLevelInfoName=" + this.memberLevelInfoName + ",levelName=" + this.levelName + ",dataTime=" + this.dataTime + ",userShowFlag=" + this.userShowFlag + ",sysShowFlag=" + this.sysShowFlag + ",timeToUpdate=" + this.timeToUpdate + ",nickName=" + this.nickName + ",specialTitle=" + this.specialTitle + ",levelNameNew=" + this.levelNameNew + ",userShowFlagNew=" + this.userShowFlagNew + ",msgNeedField=" + this.msgNeedField + ",cmdUinFlagExt3Grocery=" + this.cmdUinFlagExt3Grocery + ",memberIcon=" + this.memberIcon + ",memberInfoSeq=" + this.memberInfoSeq + ",}";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
|
||||
public class MemberIcon {
|
||||
public int bizId;
|
||||
public byte[] exInfo = new byte[0];
|
||||
public long expireTime;
|
||||
public int resId;
|
||||
|
||||
public int getBizId() {
|
||||
return this.bizId;
|
||||
}
|
||||
|
||||
public byte[] getExInfo() {
|
||||
return this.exInfo;
|
||||
}
|
||||
|
||||
public long getExpireTime() {
|
||||
return this.expireTime;
|
||||
}
|
||||
|
||||
public int getResId() {
|
||||
return this.resId;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "MemberIcon{resId=" + this.resId + ",expireTime=" + this.expireTime + ",bizId=" + this.bizId + ",exInfo=" + this.exInfo + ",}";
|
||||
}
|
||||
|
||||
}
|
@ -1,51 +1,146 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
|
||||
public final class MemberInfo implements IKernelModel {
|
||||
String avatarPath;
|
||||
String cardName;
|
||||
int cardType;
|
||||
boolean isDelete;
|
||||
boolean isSpecialConcerned;
|
||||
String nick;
|
||||
String qid;
|
||||
String remark;
|
||||
MemberRole role;
|
||||
long serialVersionUID;
|
||||
int shutUpTime;
|
||||
String uid;
|
||||
long uin;
|
||||
import com.tencent.qqnt.kernelpublic.nativeinterface.MemberRole;
|
||||
|
||||
public MemberInfo() {
|
||||
this.serialVersionUID = 1L;
|
||||
this.uid = "";
|
||||
this.qid = "";
|
||||
this.nick = "";
|
||||
this.remark = "";
|
||||
this.cardName = "";
|
||||
this.role = MemberRole.values()[0];
|
||||
this.avatarPath = "";
|
||||
public final class MemberInfo implements IKernelModel {
|
||||
public int bigClubFlag;
|
||||
public int bigClubLevel;
|
||||
public int cardNameId;
|
||||
public int cardType;
|
||||
public int creditLevel;
|
||||
public int globalGroupLevel;
|
||||
public int globalGroupPoint;
|
||||
public boolean isDelete;
|
||||
public boolean isRobot;
|
||||
public boolean isSpecialConcerned;
|
||||
public boolean isSpecialShielded;
|
||||
public int joinTime;
|
||||
public int lastSpeakTime;
|
||||
public int memberFlag;
|
||||
public int memberFlagExt;
|
||||
public int memberFlagExt2;
|
||||
public int memberLevel;
|
||||
public int memberMobileFlag;
|
||||
public int memberTitleId;
|
||||
public int mssVipType;
|
||||
public int richFlag;
|
||||
public int shutUpTime;
|
||||
public long specialTitleExpireTime;
|
||||
public long uin;
|
||||
public int userShowFlag;
|
||||
public int userShowFlagNew;
|
||||
long serialVersionUID = 1;
|
||||
public String uid = "";
|
||||
public String qid = "";
|
||||
public String nick = "";
|
||||
public String remark = "";
|
||||
public String cardName = "";
|
||||
public MemberRole role = MemberRole.values()[0];
|
||||
public String avatarPath = "";
|
||||
public byte[] groupHonor = new byte[0];
|
||||
public String memberSpecialTitle = "";
|
||||
public String autoRemark = "";
|
||||
|
||||
public String getAutoRemark() {
|
||||
return this.autoRemark;
|
||||
}
|
||||
|
||||
public String getAvatarPath() {
|
||||
return this.avatarPath;
|
||||
}
|
||||
|
||||
public int getBigClubFlag() {
|
||||
return this.bigClubFlag;
|
||||
}
|
||||
|
||||
public int getBigClubLevel() {
|
||||
return this.bigClubLevel;
|
||||
}
|
||||
|
||||
public String getCardName() {
|
||||
return this.cardName;
|
||||
}
|
||||
|
||||
public int getCardNameId() {
|
||||
return this.cardNameId;
|
||||
}
|
||||
|
||||
public int getCardType() {
|
||||
return this.cardType;
|
||||
}
|
||||
|
||||
public int getCreditLevel() {
|
||||
return this.creditLevel;
|
||||
}
|
||||
|
||||
public int getGlobalGroupLevel() {
|
||||
return this.globalGroupLevel;
|
||||
}
|
||||
|
||||
public int getGlobalGroupPoint() {
|
||||
return this.globalGroupPoint;
|
||||
}
|
||||
|
||||
public byte[] getGroupHonor() {
|
||||
return this.groupHonor;
|
||||
}
|
||||
|
||||
public boolean getIsDelete() {
|
||||
return this.isDelete;
|
||||
}
|
||||
|
||||
public boolean getIsRobot() {
|
||||
return this.isRobot;
|
||||
}
|
||||
|
||||
public boolean getIsSpecialConcerned() {
|
||||
return this.isSpecialConcerned;
|
||||
}
|
||||
|
||||
public boolean getIsSpecialShielded() {
|
||||
return this.isSpecialShielded;
|
||||
}
|
||||
|
||||
public int getJoinTime() {
|
||||
return this.joinTime;
|
||||
}
|
||||
|
||||
public int getLastSpeakTime() {
|
||||
return this.lastSpeakTime;
|
||||
}
|
||||
|
||||
public int getMemberFlag() {
|
||||
return this.memberFlag;
|
||||
}
|
||||
|
||||
public int getMemberFlagExt() {
|
||||
return this.memberFlagExt;
|
||||
}
|
||||
|
||||
public int getMemberFlagExt2() {
|
||||
return this.memberFlagExt2;
|
||||
}
|
||||
|
||||
public int getMemberLevel() {
|
||||
return this.memberLevel;
|
||||
}
|
||||
|
||||
public int getMemberMobileFlag() {
|
||||
return this.memberMobileFlag;
|
||||
}
|
||||
|
||||
public String getMemberSpecialTitle() {
|
||||
return this.memberSpecialTitle;
|
||||
}
|
||||
|
||||
public int getMemberTitleId() {
|
||||
return this.memberTitleId;
|
||||
}
|
||||
|
||||
public int getMssVipType() {
|
||||
return this.mssVipType;
|
||||
}
|
||||
|
||||
public String getNick() {
|
||||
return this.nick;
|
||||
}
|
||||
@ -58,6 +153,10 @@ public final class MemberInfo implements IKernelModel {
|
||||
return this.remark;
|
||||
}
|
||||
|
||||
public int getRichFlag() {
|
||||
return this.richFlag;
|
||||
}
|
||||
|
||||
public MemberRole getRole() {
|
||||
return this.role;
|
||||
}
|
||||
@ -66,6 +165,10 @@ public final class MemberInfo implements IKernelModel {
|
||||
return this.shutUpTime;
|
||||
}
|
||||
|
||||
public long getSpecialTitleExpireTime() {
|
||||
return this.specialTitleExpireTime;
|
||||
}
|
||||
|
||||
public String getUid() {
|
||||
return this.uid;
|
||||
}
|
||||
@ -74,26 +177,114 @@ public final class MemberInfo implements IKernelModel {
|
||||
return this.uin;
|
||||
}
|
||||
|
||||
public int getUserShowFlag() {
|
||||
return this.userShowFlag;
|
||||
}
|
||||
|
||||
public int getUserShowFlagNew() {
|
||||
return this.userShowFlagNew;
|
||||
}
|
||||
|
||||
public void setAutoRemark(String str) {
|
||||
this.autoRemark = str;
|
||||
}
|
||||
|
||||
public void setAvatarPath(String str) {
|
||||
this.avatarPath = str;
|
||||
}
|
||||
|
||||
public void setBigClubFlag(int i2) {
|
||||
this.bigClubFlag = i2;
|
||||
}
|
||||
|
||||
public void setBigClubLevel(int i2) {
|
||||
this.bigClubLevel = i2;
|
||||
}
|
||||
|
||||
public void setCardName(String str) {
|
||||
this.cardName = str;
|
||||
}
|
||||
|
||||
public void setCardNameId(int i2) {
|
||||
this.cardNameId = i2;
|
||||
}
|
||||
|
||||
public void setCardType(int i2) {
|
||||
this.cardType = i2;
|
||||
}
|
||||
|
||||
public void setCreditLevel(int i2) {
|
||||
this.creditLevel = i2;
|
||||
}
|
||||
|
||||
public void setGlobalGroupLevel(int i2) {
|
||||
this.globalGroupLevel = i2;
|
||||
}
|
||||
|
||||
public void setGlobalGroupPoint(int i2) {
|
||||
this.globalGroupPoint = i2;
|
||||
}
|
||||
|
||||
public void setGroupHonor(byte[] bArr) {
|
||||
this.groupHonor = bArr;
|
||||
}
|
||||
|
||||
public void setIsDelete(boolean z) {
|
||||
this.isDelete = z;
|
||||
}
|
||||
|
||||
public void setIsRobot(boolean z) {
|
||||
this.isRobot = z;
|
||||
}
|
||||
|
||||
public void setIsSpecialConcerned(boolean z) {
|
||||
this.isSpecialConcerned = z;
|
||||
}
|
||||
|
||||
public void setIsSpecialShielded(boolean z) {
|
||||
this.isSpecialShielded = z;
|
||||
}
|
||||
|
||||
public void setJoinTime(int i2) {
|
||||
this.joinTime = i2;
|
||||
}
|
||||
|
||||
public void setLastSpeakTime(int i2) {
|
||||
this.lastSpeakTime = i2;
|
||||
}
|
||||
|
||||
public void setMemberFlag(int i2) {
|
||||
this.memberFlag = i2;
|
||||
}
|
||||
|
||||
public void setMemberFlagExt(int i2) {
|
||||
this.memberFlagExt = i2;
|
||||
}
|
||||
|
||||
public void setMemberFlagExt2(int i2) {
|
||||
this.memberFlagExt2 = i2;
|
||||
}
|
||||
|
||||
public void setMemberLevel(int i2) {
|
||||
this.memberLevel = i2;
|
||||
}
|
||||
|
||||
public void setMemberMobileFlag(int i2) {
|
||||
this.memberMobileFlag = i2;
|
||||
}
|
||||
|
||||
public void setMemberSpecialTitle(String str) {
|
||||
this.memberSpecialTitle = str;
|
||||
}
|
||||
|
||||
public void setMemberTitleId(int i2) {
|
||||
this.memberTitleId = i2;
|
||||
}
|
||||
|
||||
public void setMssVipType(int i2) {
|
||||
this.mssVipType = i2;
|
||||
}
|
||||
|
||||
public void setNick(String str) {
|
||||
this.nick = str;
|
||||
}
|
||||
@ -106,6 +297,10 @@ public final class MemberInfo implements IKernelModel {
|
||||
this.remark = str;
|
||||
}
|
||||
|
||||
public void setRichFlag(int i2) {
|
||||
this.richFlag = i2;
|
||||
}
|
||||
|
||||
public void setRole(MemberRole memberRole) {
|
||||
this.role = memberRole;
|
||||
}
|
||||
@ -114,6 +309,10 @@ public final class MemberInfo implements IKernelModel {
|
||||
this.shutUpTime = i2;
|
||||
}
|
||||
|
||||
public void setSpecialTitleExpireTime(long j2) {
|
||||
this.specialTitleExpireTime = j2;
|
||||
}
|
||||
|
||||
public void setUid(String str) {
|
||||
this.uid = str;
|
||||
}
|
||||
@ -122,30 +321,16 @@ public final class MemberInfo implements IKernelModel {
|
||||
this.uin = j2;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "MemberInfo{uid=" + this.uid + ",qid=" + this.qid + ",uin=" + this.uin + ",nick=" + this.nick + ",remark=" + this.remark + ",cardType=" + this.cardType + ",cardName=" + this.cardName + ",role=" + this.role + ",avatarPath=" + this.avatarPath + ",shutUpTime=" + this.shutUpTime + ",isDelete=" + this.isDelete + ",isSpecialConcerned=" + this.isSpecialConcerned + ",}";
|
||||
public void setUserShowFlag(int i2) {
|
||||
this.userShowFlag = i2;
|
||||
}
|
||||
|
||||
public MemberInfo(String str, String str2, long j2, String str3, String str4, int i2, String str5, MemberRole memberRole, String str6, int i3, boolean z, boolean z2) {
|
||||
this.serialVersionUID = 1L;
|
||||
this.uid = "";
|
||||
this.qid = "";
|
||||
this.nick = "";
|
||||
this.remark = "";
|
||||
this.cardName = "";
|
||||
this.role = MemberRole.values()[0];
|
||||
this.avatarPath = "";
|
||||
this.uid = str;
|
||||
this.qid = str2;
|
||||
this.uin = j2;
|
||||
this.nick = str3;
|
||||
this.remark = str4;
|
||||
this.cardType = i2;
|
||||
this.cardName = str5;
|
||||
this.role = memberRole;
|
||||
this.avatarPath = str6;
|
||||
this.shutUpTime = i3;
|
||||
this.isDelete = z;
|
||||
this.isSpecialConcerned = z2;
|
||||
public void setUserShowFlagNew(int i2) {
|
||||
this.userShowFlagNew = i2;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "MemberInfo{uid=" + this.uid + ",qid=" + this.qid + ",uin=" + this.uin + ",nick=" + this.nick + ",remark=" + this.remark + ",cardType=" + this.cardType + ",cardName=" + this.cardName + ",role=" + this.role + ",avatarPath=" + this.avatarPath + ",shutUpTime=" + this.shutUpTime + ",isDelete=" + this.isDelete + ",isSpecialConcerned=" + this.isSpecialConcerned + ",isRobot=" + this.isRobot + ",groupHonor=" + this.groupHonor + ",memberLevel=" + this.memberLevel + ",globalGroupLevel=" + this.globalGroupLevel + ",globalGroupPoint=" + this.globalGroupPoint + ",memberTitleId=" + this.memberTitleId + ",memberSpecialTitle=" + this.memberSpecialTitle + ",specialTitleExpireTime=" + this.specialTitleExpireTime + ",userShowFlag=" + this.userShowFlag + ",userShowFlagNew=" + this.userShowFlagNew + ",richFlag=" + this.richFlag + ",mssVipType=" + this.mssVipType + ",bigClubLevel=" + this.bigClubLevel + ",bigClubFlag=" + this.bigClubFlag + ",autoRemark=" + this.autoRemark + ",creditLevel=" + this.creditLevel + ",joinTime=" + this.joinTime + ",lastSpeakTime=" + this.lastSpeakTime + ",memberFlag=" + this.memberFlag + ",memberFlagExt=" + this.memberFlagExt + ",memberMobileFlag=" + this.memberMobileFlag + ",memberFlagExt2=" + this.memberFlagExt2 + ",isSpecialShielded=" + this.isSpecialShielded + ",cardNameId=" + this.cardNameId + ",}";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
|
||||
public class MemberLevelName {
|
||||
public int level;
|
||||
public String strName = "";
|
||||
|
||||
public int getLevel() {
|
||||
return this.level;
|
||||
}
|
||||
|
||||
public String getStrName() {
|
||||
return this.strName;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "MemberLevelName{level=" + this.level + ",strName=" + this.strName + ",}";
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
|
||||
import com.tencent.qqnt.kernelpublic.nativeinterface.Contact;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
@ -0,0 +1,4 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
|
||||
public class QueryUserSecQualityRsp {
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
|
||||
|
||||
import com.tencent.qqnt.kernelpublic.nativeinterface.Contact;
|
||||
|
||||
public final class UnreadCntInfo {
|
||||
UnreadCnt allUnreadCnt;
|
||||
|
@ -1,4 +1,6 @@
|
||||
package com.tencent.qqnt.kernel.nativeinterface;
|
||||
package com.tencent.qqnt.kernelpublic.nativeinterface;
|
||||
|
||||
import com.tencent.qqnt.kernel.nativeinterface.IKernelModel;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
@ -0,0 +1,9 @@
|
||||
package com.tencent.qqnt.kernelpublic.nativeinterface;
|
||||
|
||||
public enum MemberRole {
|
||||
UNSPECIFIED,
|
||||
STRANGER,
|
||||
MEMBER,
|
||||
ADMIN,
|
||||
OWNER
|
||||
}
|
@ -2,6 +2,7 @@ package com.tencent.qqnt.msg.api;
|
||||
|
||||
import com.tencent.mobileqq.qroute.QRouteApi;
|
||||
import com.tencent.qqnt.kernel.nativeinterface.*;
|
||||
import com.tencent.qqnt.kernelpublic.nativeinterface.Contact;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@ -9,7 +10,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
import kotlin.Pair;
|
||||
import kotlinx.coroutines.flow.Flow;
|
||||
|
||||
public interface IMsgService extends QRouteApi {
|
||||
|
@ -26,7 +26,7 @@ buildscript {
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.android.tools:r8:8.2.47")
|
||||
classpath("com.android.tools:r8:8.3.37")
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,11 +34,9 @@ rootProject.name = "Shamrock"
|
||||
include(
|
||||
":app",
|
||||
":xposed",
|
||||
":qqinterface"
|
||||
)
|
||||
include(":protobuf")
|
||||
include(":processor")
|
||||
include(":annotations")
|
||||
include(":kritor")
|
||||
|
||||
project(":kritor").projectDir = file("kritor/protos")
|
||||
":qqinterface",
|
||||
":protobuf",
|
||||
":processor",
|
||||
":annotations",
|
||||
":kritor"
|
||||
)
|
@ -5,7 +5,6 @@ plugins {
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("kotlin-kapt")
|
||||
id("com.google.devtools.ksp") version "1.9.22-1.0.17"
|
||||
id("com.google.protobuf") version "0.9.4"
|
||||
kotlin("plugin.serialization") version "1.9.22"
|
||||
}
|
||||
|
||||
@ -61,11 +60,10 @@ kotlin {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly ("de.robv.android.xposed:api:82")
|
||||
compileOnly (project(":qqinterface"))
|
||||
|
||||
protobuf(project(":kritor"))
|
||||
compileOnly("de.robv.android.xposed:api:82")
|
||||
compileOnly(project(":qqinterface"))
|
||||
|
||||
implementation(project(":kritor"))
|
||||
implementation(project(":protobuf"))
|
||||
implementation(project(":annotations"))
|
||||
ksp(project(":processor"))
|
||||
@ -75,24 +73,20 @@ dependencies {
|
||||
DEPENDENCY_ANDROIDX.forEach {
|
||||
implementation(it)
|
||||
}
|
||||
//implementation(DEPENDENCY_PROTOBUF)
|
||||
|
||||
implementation(room("runtime"))
|
||||
kapt(room("compiler"))
|
||||
implementation(room("ktx"))
|
||||
|
||||
implementation(kotlinx("io-jvm", "0.1.16"))
|
||||
implementation(kotlinx("serialization-protobuf", "1.6.2"))
|
||||
|
||||
implementation(ktor("client", "core"))
|
||||
implementation(ktor("client", "okhttp"))
|
||||
implementation(ktor("serialization", "kotlinx-json"))
|
||||
|
||||
implementation("io.grpc:grpc-stub:1.62.2")
|
||||
implementation("io.grpc:grpc-protobuf-lite:1.62.2")
|
||||
implementation("com.google.protobuf:protobuf-kotlin-lite:3.25.3")
|
||||
implementation("io.grpc:grpc-kotlin-stub:1.4.1")
|
||||
implementation("io.grpc:grpc-okhttp:1.62.2")
|
||||
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")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||
@ -106,40 +100,3 @@ tasks.withType<KotlinCompile>().all {
|
||||
freeCompilerArgs = listOf("-opt-in=kotlin.RequiresOptIn")
|
||||
}
|
||||
}
|
||||
|
||||
protobuf {
|
||||
protoc {
|
||||
artifact = "com.google.protobuf:protoc:3.25.3"
|
||||
}
|
||||
plugins {
|
||||
create("java") {
|
||||
artifact = "io.grpc:protoc-gen-grpc-java:1.62.2"
|
||||
}
|
||||
create("grpc") {
|
||||
artifact = "io.grpc:protoc-gen-grpc-java:1.62.2"
|
||||
}
|
||||
create("grpckt") {
|
||||
artifact = "io.grpc:protoc-gen-grpc-kotlin:1.4.1:jdk8@jar"
|
||||
}
|
||||
}
|
||||
generateProtoTasks {
|
||||
all().forEach {
|
||||
it.plugins {
|
||||
create("java") {
|
||||
option("lite")
|
||||
}
|
||||
create("grpc") {
|
||||
option("lite")
|
||||
}
|
||||
create("grpckt") {
|
||||
option("lite")
|
||||
}
|
||||
}
|
||||
it.builtins {
|
||||
create("kotlin") {
|
||||
option("lite")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ active_ticket=
|
||||
enable_self_message=false
|
||||
|
||||
# 旧BDH兼容开关
|
||||
enable_old_bdh=false
|
||||
enable_old_bdh=true
|
||||
|
||||
# 反JVM调用栈跟踪
|
||||
anti_jvm_trace=true
|
||||
|
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 {
|
||||
|
||||
|
||||
}
|
@ -2,7 +2,12 @@
|
||||
package kritor.server
|
||||
|
||||
import io.grpc.Grpc
|
||||
import io.grpc.Metadata
|
||||
import io.grpc.InsecureServerCredentials
|
||||
import io.grpc.ServerCall
|
||||
import io.grpc.ServerCallHandler
|
||||
import io.grpc.ServerInterceptor
|
||||
import io.grpc.ForwardingServerCall.SimpleForwardingServerCall;
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
@ -10,22 +15,45 @@ import kotlinx.coroutines.asExecutor
|
||||
import kritor.auth.AuthInterceptor
|
||||
import kritor.service.*
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.tools.ShamrockVersion
|
||||
import qq.service.ticket.TicketHelper
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class KritorServer(
|
||||
private val port: Int
|
||||
): CoroutineScope {
|
||||
|
||||
private val serverInterceptor = object : ServerInterceptor {
|
||||
override fun <ReqT, RespT> interceptCall(
|
||||
call: ServerCall<ReqT, RespT>, headers: Metadata, next: ServerCallHandler<ReqT, RespT>
|
||||
): ServerCall.Listener<ReqT> {
|
||||
return next.startCall(object : SimpleForwardingServerCall<ReqT, RespT>(call) {
|
||||
override fun sendHeaders(headers: Metadata?) {
|
||||
headers?.apply {
|
||||
put(Metadata.Key.of("kritor-self-uin", Metadata.ASCII_STRING_MARSHALLER), TicketHelper.getUin())
|
||||
put(Metadata.Key.of("kritor-self-uid", Metadata.ASCII_STRING_MARSHALLER), TicketHelper.getUid())
|
||||
put(Metadata.Key.of("kritor-self-version", Metadata.ASCII_STRING_MARSHALLER), "OpenShamrock-$ShamrockVersion")
|
||||
}
|
||||
super.sendHeaders(headers)
|
||||
}
|
||||
}, headers)
|
||||
}
|
||||
}
|
||||
|
||||
private val server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create())
|
||||
.executor(Dispatchers.IO.asExecutor())
|
||||
.intercept(AuthInterceptor)
|
||||
.addService(Authentication)
|
||||
.addService(ContactService)
|
||||
.addService(KritorService)
|
||||
.intercept(serverInterceptor)
|
||||
.addService(AuthenticationService)
|
||||
.addService(CoreService)
|
||||
.addService(FriendService)
|
||||
.addService(GroupService)
|
||||
.addService(GroupFileService)
|
||||
.addService(MessageService)
|
||||
.addService(EventService)
|
||||
.addService(WebService)
|
||||
.addService(DeveloperService)
|
||||
.addService(QsignService)
|
||||
.build()!!
|
||||
|
||||
fun start(block: Boolean = false) {
|
||||
|
@ -1,66 +0,0 @@
|
||||
package kritor.service
|
||||
|
||||
import io.grpc.Status
|
||||
import io.grpc.StatusRuntimeException
|
||||
import io.kritor.AuthCode
|
||||
import io.kritor.AuthReq
|
||||
import io.kritor.AuthRsp
|
||||
import io.kritor.AuthenticationGrpcKt
|
||||
import io.kritor.GetAuthStateReq
|
||||
import io.kritor.GetAuthStateRsp
|
||||
import io.kritor.authRsp
|
||||
import io.kritor.getAuthStateRsp
|
||||
import kritor.auth.AuthInterceptor
|
||||
import moe.fuqiuluo.shamrock.config.ActiveTicket
|
||||
import moe.fuqiuluo.shamrock.config.ShamrockConfig
|
||||
import qq.service.QQInterfaces
|
||||
|
||||
object Authentication: AuthenticationGrpcKt.AuthenticationCoroutineImplBase() {
|
||||
@Grpc("Authentication", "Auth")
|
||||
override suspend fun auth(request: AuthReq): AuthRsp {
|
||||
if (QQInterfaces.app.account != request.account) {
|
||||
return authRsp {
|
||||
code = AuthCode.NO_ACCOUNT
|
||||
msg = "No such account"
|
||||
}
|
||||
}
|
||||
|
||||
val activeTicketName = ActiveTicket.name()
|
||||
var index = 0
|
||||
while (true) {
|
||||
val ticket = ShamrockConfig.getProperty(activeTicketName + if (index == 0) "" else ".$index", null)
|
||||
if (ticket.isNullOrEmpty()) {
|
||||
if (index == 0) {
|
||||
return authRsp {
|
||||
code = AuthCode.OK
|
||||
msg = "OK"
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
} else if (ticket == request.ticket) {
|
||||
return authRsp {
|
||||
code = AuthCode.OK
|
||||
msg = "OK"
|
||||
}
|
||||
}
|
||||
index++
|
||||
}
|
||||
|
||||
return authRsp {
|
||||
code = AuthCode.NO_TICKET
|
||||
msg = "Invalid ticket"
|
||||
}
|
||||
}
|
||||
|
||||
@Grpc("Authentication", "GetAuthState")
|
||||
override suspend fun getAuthState(request: GetAuthStateReq): GetAuthStateRsp {
|
||||
if (request.account != QQInterfaces.app.account) {
|
||||
throw StatusRuntimeException(Status.CANCELLED.withDescription("No such account"))
|
||||
}
|
||||
|
||||
return getAuthStateRsp {
|
||||
isRequiredAuth = AuthInterceptor.getAllTicket().isNotEmpty()
|
||||
}
|
||||
}
|
||||
}
|
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()
|
||||
}
|
||||
}
|
@ -1,183 +0,0 @@
|
||||
package kritor.service
|
||||
|
||||
import android.os.Bundle
|
||||
import com.tencent.mobileqq.profilecard.api.IProfileCardBlacklistApi
|
||||
import com.tencent.mobileqq.profilecard.api.IProfileProtocolConst.*
|
||||
import com.tencent.mobileqq.profilecard.api.IProfileProtocolService
|
||||
import com.tencent.mobileqq.qroute.QRoute
|
||||
import io.grpc.Status
|
||||
import io.grpc.StatusRuntimeException
|
||||
import io.kritor.contact.ContactServiceGrpcKt
|
||||
import io.kritor.contact.GetUidRequest
|
||||
import io.kritor.contact.GetUidResponse
|
||||
import io.kritor.contact.GetUinByUidRequest
|
||||
import io.kritor.contact.GetUinByUidResponse
|
||||
import io.kritor.contact.IsBlackListUserRequest
|
||||
import io.kritor.contact.IsBlackListUserResponse
|
||||
import io.kritor.contact.ProfileCard
|
||||
import io.kritor.contact.ProfileCardRequest
|
||||
import io.kritor.contact.SetProfileCardRequest
|
||||
import io.kritor.contact.SetProfileCardResponse
|
||||
import io.kritor.contact.StrangerExt
|
||||
import io.kritor.contact.StrangerInfo
|
||||
import io.kritor.contact.StrangerInfoRequest
|
||||
import io.kritor.contact.VoteUserRequest
|
||||
import io.kritor.contact.VoteUserResponse
|
||||
import io.kritor.contact.profileCard
|
||||
import io.kritor.contact.strangerInfo
|
||||
import io.kritor.contact.voteUserResponse
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import qq.service.QQInterfaces
|
||||
import qq.service.contact.ContactHelper
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
object ContactService: ContactServiceGrpcKt.ContactServiceCoroutineImplBase() {
|
||||
@Grpc("ContactService", "VoteUser")
|
||||
override suspend fun voteUser(request: VoteUserRequest): VoteUserResponse {
|
||||
ContactHelper.voteUser(when(request.accountCase!!) {
|
||||
VoteUserRequest.AccountCase.ACCOUNT_UIN -> request.accountUin
|
||||
VoteUserRequest.AccountCase.ACCOUNT_UID -> ContactHelper.getUinByUidAsync(request.accountUid).toLong()
|
||||
VoteUserRequest.AccountCase.ACCOUNT_NOT_SET -> throw StatusRuntimeException(Status.INVALID_ARGUMENT
|
||||
.withDescription("account not set")
|
||||
)
|
||||
}, request.voteCount).onFailure {
|
||||
throw StatusRuntimeException(Status.INTERNAL
|
||||
.withDescription(it.stackTraceToString())
|
||||
)
|
||||
}
|
||||
return voteUserResponse { }
|
||||
}
|
||||
|
||||
@Grpc("ContactService", "GetProfileCard")
|
||||
override suspend fun getProfileCard(request: ProfileCardRequest): ProfileCard {
|
||||
val uin = when (request.accountCase!!) {
|
||||
ProfileCardRequest.AccountCase.ACCOUNT_UIN -> request.accountUin
|
||||
ProfileCardRequest.AccountCase.ACCOUNT_UID -> ContactHelper.getUinByUidAsync(request.accountUid).toLong()
|
||||
ProfileCardRequest.AccountCase.ACCOUNT_NOT_SET -> throw StatusRuntimeException(Status.INVALID_ARGUMENT
|
||||
.withDescription("account not set")
|
||||
)
|
||||
}
|
||||
|
||||
val contact = ContactHelper.getProfileCard(uin)
|
||||
|
||||
contact.onFailure {
|
||||
throw StatusRuntimeException(Status.INTERNAL
|
||||
.withDescription(it.stackTraceToString())
|
||||
)
|
||||
}
|
||||
|
||||
contact.onSuccess {
|
||||
return profileCard {
|
||||
this.uin = it.uin.toLong()
|
||||
this.uid = if (request.hasAccountUid()) request.accountUid
|
||||
else ContactHelper.getUidByUinAsync(it.uin.toLong())
|
||||
this.name = it.strNick ?: ""
|
||||
this.remark = it.strReMark ?: ""
|
||||
this.level = it.iQQLevel
|
||||
this.birthday = it.lBirthday
|
||||
this.loginDay = it.lLoginDays.toInt()
|
||||
this.voteCnt = it.lVoteCount.toInt()
|
||||
this.qid = it.qid ?: ""
|
||||
this.isSchoolVerified = it.schoolVerifiedFlag
|
||||
}
|
||||
}
|
||||
|
||||
throw StatusRuntimeException(Status.INTERNAL
|
||||
.withDescription("logic failed")
|
||||
)
|
||||
}
|
||||
|
||||
@Grpc("ContactService", "GetStrangerInfo")
|
||||
override suspend fun getStrangerInfo(request: StrangerInfoRequest): StrangerInfo {
|
||||
val userId = request.uin
|
||||
val info = ContactHelper.refreshAndGetProfileCard(userId).onFailure {
|
||||
throw StatusRuntimeException(Status.INTERNAL
|
||||
.withCause(it)
|
||||
.withDescription("Unable to fetch stranger info")
|
||||
)
|
||||
}.getOrThrow()
|
||||
|
||||
return strangerInfo {
|
||||
this.uid = ContactHelper.getUidByUinAsync(userId)
|
||||
this.uin = (info.uin ?: "0").toLong()
|
||||
this.name = info.strNick ?: ""
|
||||
this.level = info.iQQLevel
|
||||
this.loginDay = info.lLoginDays.toInt()
|
||||
this.voteCnt = info.lVoteCount.toInt()
|
||||
this.qid = info.qid ?: ""
|
||||
this.isSchoolVerified = info.schoolVerifiedFlag
|
||||
this.ext = StrangerExt.newBuilder()
|
||||
.setBigVip(info.bBigClubVipOpen == 1.toByte())
|
||||
.setHollywoodVip(info.bHollywoodVipOpen == 1.toByte())
|
||||
.setQqVip(info.bQQVipOpen == 1.toByte())
|
||||
.setSuperVip(info.bSuperQQOpen == 1.toByte())
|
||||
.setVoted(info.bVoted == 1.toByte())
|
||||
.build().toByteString()
|
||||
}
|
||||
}
|
||||
|
||||
@Grpc("ContactService", "GetUid")
|
||||
override suspend fun getUid(request: GetUidRequest): GetUidResponse {
|
||||
return GetUidResponse.newBuilder().apply {
|
||||
request.uinList.forEach {
|
||||
putUid(it, ContactHelper.getUidByUinAsync(it))
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
@Grpc("ContactService", "GetUinByUid")
|
||||
override suspend fun getUinByUid(request: GetUinByUidRequest): GetUinByUidResponse {
|
||||
return GetUinByUidResponse.newBuilder().apply {
|
||||
request.uidList.forEach {
|
||||
putUin(it, ContactHelper.getUinByUidAsync(it).toLong())
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
@Grpc("ContactService", "SetProfileCard")
|
||||
override suspend fun setProfileCard(request: SetProfileCardRequest): SetProfileCardResponse {
|
||||
val bundle = Bundle()
|
||||
val service = QQInterfaces.app
|
||||
.getRuntimeService(IProfileProtocolService::class.java, "all")
|
||||
if (request.hasNickName()) {
|
||||
bundle.putString(KEY_NICK, request.nickName)
|
||||
}
|
||||
if (request.hasCompany()) {
|
||||
bundle.putString(KEY_COMPANY, request.company)
|
||||
}
|
||||
if (request.hasEmail()) {
|
||||
bundle.putString(KEY_EMAIL, request.email)
|
||||
}
|
||||
if (request.hasCollege()) {
|
||||
bundle.putString(KEY_COLLEGE, request.college)
|
||||
}
|
||||
if (request.hasPersonalNote()) {
|
||||
bundle.putString(KEY_PERSONAL_NOTE, request.personalNote)
|
||||
}
|
||||
|
||||
if (request.hasBirthday()) {
|
||||
bundle.putInt(KEY_BIRTHDAY, request.birthday)
|
||||
}
|
||||
if (request.hasAge()) {
|
||||
bundle.putInt(KEY_AGE, request.age)
|
||||
}
|
||||
|
||||
service.setProfileDetail(bundle)
|
||||
return super.setProfileCard(request)
|
||||
}
|
||||
|
||||
@Grpc("ContactService", "IsBlackListUser")
|
||||
override suspend fun isBlackListUser(request: IsBlackListUserRequest): IsBlackListUserResponse {
|
||||
val blacklistApi = QRoute.api(IProfileCardBlacklistApi::class.java)
|
||||
val isBlack = withTimeoutOrNull(5000) {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
blacklistApi.isBlackOrBlackedUin(request.uin.toString()) {
|
||||
continuation.resume(it)
|
||||
}
|
||||
}
|
||||
} ?: false
|
||||
return IsBlackListUserResponse.newBuilder().setIsBlackListUser(isBlack).build()
|
||||
}
|
||||
}
|
@ -4,60 +4,35 @@ import android.util.Base64
|
||||
import com.tencent.mobileqq.app.QQAppInterface
|
||||
import io.grpc.Status
|
||||
import io.grpc.StatusRuntimeException
|
||||
import io.kritor.core.ClearCacheRequest
|
||||
import io.kritor.core.ClearCacheResponse
|
||||
import io.kritor.core.DownloadFileRequest
|
||||
import io.kritor.core.DownloadFileResponse
|
||||
import io.kritor.core.GetCurrentAccountRequest
|
||||
import io.kritor.core.GetCurrentAccountResponse
|
||||
import io.kritor.core.GetVersionRequest
|
||||
import io.kritor.core.GetVersionResponse
|
||||
import io.kritor.core.KritorServiceGrpcKt
|
||||
import io.kritor.core.SwitchAccountRequest
|
||||
import io.kritor.core.SwitchAccountResponse
|
||||
import io.kritor.core.clearCacheResponse
|
||||
import io.kritor.core.downloadFileResponse
|
||||
import io.kritor.core.getCurrentAccountResponse
|
||||
import io.kritor.core.getVersionResponse
|
||||
import io.kritor.core.switchAccountResponse
|
||||
import 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 moe.fuqiuluo.shamrock.utils.MMKVFetcher
|
||||
import mqq.app.MobileQQ
|
||||
import qq.service.QQInterfaces
|
||||
import qq.service.QQInterfaces.Companion.app
|
||||
import qq.service.contact.ContactHelper
|
||||
import java.io.File
|
||||
|
||||
object KritorService: KritorServiceGrpcKt.KritorServiceCoroutineImplBase() {
|
||||
@Grpc("KritorService", "GetVersion")
|
||||
internal object CoreService : CoreServiceGrpcKt.CoreServiceCoroutineImplBase() {
|
||||
@Grpc("CoreService", "GetVersion")
|
||||
override suspend fun getVersion(request: GetVersionRequest): GetVersionResponse {
|
||||
return getVersionResponse {
|
||||
return GetVersionResponse.newBuilder().apply {
|
||||
this.version = ShamrockVersion
|
||||
this.appName = "Shamrock"
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
@Grpc("KritorService", "ClearCache")
|
||||
override suspend fun clearCache(request: ClearCacheRequest): ClearCacheResponse {
|
||||
FileUtils.clearCache()
|
||||
MMKVFetcher.mmkvWithId("audio2silk")
|
||||
.clear()
|
||||
return clearCacheResponse {}
|
||||
}
|
||||
|
||||
@Grpc("KritorService", "GetCurrentAccount")
|
||||
@Grpc("CoreService", "GetCurrentAccount")
|
||||
override suspend fun getCurrentAccount(request: GetCurrentAccountRequest): GetCurrentAccountResponse {
|
||||
return 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("KritorService", "DownloadFile")
|
||||
@Grpc("CoreService", "DownloadFile")
|
||||
override suspend fun downloadFile(request: DownloadFileRequest): DownloadFileResponse {
|
||||
val headerMap = mutableMapOf(
|
||||
"User-Agent" to "Shamrock"
|
||||
@ -76,13 +51,14 @@ object KritorService: KritorServiceGrpcKt.KritorServiceCoroutineImplBase() {
|
||||
if (request.hasBase64()) {
|
||||
val bytes = Base64.decode(request.base64, Base64.DEFAULT)
|
||||
tmp.writeBytes(bytes)
|
||||
} else if(request.hasUrl()) {
|
||||
if(!DownloadUtils.download(
|
||||
} 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"))
|
||||
}
|
||||
}
|
||||
@ -96,18 +72,22 @@ object KritorService: KritorServiceGrpcKt.KritorServiceCoroutineImplBase() {
|
||||
}
|
||||
}
|
||||
|
||||
return downloadFileResponse {
|
||||
return DownloadFileResponse.newBuilder().apply {
|
||||
this.fileMd5 = MD5.genFileMd5Hex(tmp.absolutePath)
|
||||
this.fileAbsolutePath = tmp.absolutePath
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
@Grpc("KritorService", "SwitchAccount")
|
||||
@Grpc("CoreService", "SwitchAccount")
|
||||
override suspend fun switchAccount(request: SwitchAccountRequest): SwitchAccountResponse {
|
||||
val uin = when(request.accountCase!!) {
|
||||
val uin = when (request.accountCase!!) {
|
||||
SwitchAccountRequest.AccountCase.ACCOUNT_UID -> ContactHelper.getUinByUidAsync(request.accountUid)
|
||||
SwitchAccountRequest.AccountCase.ACCOUNT_UIN -> request.accountUin.toString()
|
||||
SwitchAccountRequest.AccountCase.ACCOUNT_NOT_SET -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("account not found"))
|
||||
SwitchAccountRequest.AccountCase.ACCOUNT_NOT_SET -> throw StatusRuntimeException(
|
||||
Status.INVALID_ARGUMENT.withDescription(
|
||||
"account not found"
|
||||
)
|
||||
)
|
||||
}
|
||||
val account = MobileQQ.getMobileQQ().allAccounts.firstOrNull { it.uin == uin }
|
||||
?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("account not found"))
|
||||
@ -116,6 +96,6 @@ object KritorService: KritorServiceGrpcKt.KritorServiceCoroutineImplBase() {
|
||||
}.onFailure {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withCause(it).withDescription("failed to switch account"))
|
||||
}
|
||||
return switchAccountResponse { }
|
||||
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()
|
||||
}
|
||||
}
|
@ -1,28 +1,42 @@
|
||||
package kritor.service
|
||||
|
||||
import io.kritor.event.EventRequest
|
||||
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.eventStructure
|
||||
import io.kritor.event.RequestPushEvent
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import moe.fuqiuluo.shamrock.internals.GlobalEventTransmitter
|
||||
|
||||
object EventService: EventServiceGrpcKt.EventServiceCoroutineImplBase() {
|
||||
override fun registerActiveListener(request: EventRequest): Flow<EventStructure> {
|
||||
internal object EventService : EventServiceGrpcKt.EventServiceCoroutineImplBase() {
|
||||
override fun registerActiveListener(request: RequestPushEvent): Flow<EventStructure> {
|
||||
return channelFlow {
|
||||
when(request.type!!) {
|
||||
EventType.CORE_EVENT -> TODO()
|
||||
EventType.MESSAGE -> GlobalEventTransmitter.onMessageEvent {
|
||||
send(eventStructure {
|
||||
this.type = EventType.MESSAGE
|
||||
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.NOTICE -> TODO()
|
||||
EventType.REQUEST -> TODO()
|
||||
EventType.UNRECOGNIZED -> TODO()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +1,33 @@
|
||||
package kritor.service
|
||||
|
||||
import android.os.Bundle
|
||||
import com.tencent.mobileqq.profilecard.api.IProfileCardBlacklistApi
|
||||
import com.tencent.mobileqq.profilecard.api.IProfileProtocolConst
|
||||
import com.tencent.mobileqq.profilecard.api.IProfileProtocolService
|
||||
import com.tencent.mobileqq.qroute.QRoute
|
||||
import io.grpc.Status
|
||||
import io.grpc.StatusRuntimeException
|
||||
import io.kritor.friend.FriendServiceGrpcKt
|
||||
import io.kritor.friend.GetFriendListRequest
|
||||
import io.kritor.friend.GetFriendListResponse
|
||||
import io.kritor.friend.friendData
|
||||
import io.kritor.friend.friendExt
|
||||
import io.kritor.friend.getFriendListResponse
|
||||
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
|
||||
|
||||
object FriendService: FriendServiceGrpcKt.FriendServiceCoroutineImplBase() {
|
||||
internal object FriendService : FriendServiceGrpcKt.FriendServiceCoroutineImplBase() {
|
||||
@Grpc("FriendService", "GetFriendList")
|
||||
override suspend fun getFriendList(request: GetFriendListRequest): GetFriendListResponse {
|
||||
val friendList = FriendHelper.getFriendList(if(request.hasRefresh()) request.refresh else false).onFailure {
|
||||
throw StatusRuntimeException(Status.INTERNAL
|
||||
.withDescription(it.stackTraceToString())
|
||||
val friendList = FriendHelper.getFriendList(if (request.hasRefresh()) request.refresh else false).onFailure {
|
||||
throw StatusRuntimeException(
|
||||
Status.INTERNAL
|
||||
.withDescription(it.stackTraceToString())
|
||||
)
|
||||
}.getOrThrow()
|
||||
|
||||
return getFriendListResponse {
|
||||
return GetFriendListResponse.newBuilder().apply {
|
||||
friendList.forEach {
|
||||
this.friendList.add(friendData {
|
||||
this.addFriendsInfo(FriendInfo.newBuilder().apply {
|
||||
uin = it.uin.toLong()
|
||||
uid = ContactHelper.getUidByUinAsync(uin)
|
||||
qid = ""
|
||||
@ -32,10 +37,208 @@ object FriendService: FriendServiceGrpcKt.FriendServiceCoroutineImplBase() {
|
||||
level = 0
|
||||
gender = it.gender.toInt()
|
||||
groupId = it.groupid
|
||||
ext = friendExt {}.toByteString()
|
||||
|
||||
ext = ExtInfo.newBuilder().build()
|
||||
})
|
||||
}
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
@Grpc("FriendService", "GetFriendProfileCard")
|
||||
override suspend fun getFriendProfileCard(request: GetFriendProfileCardRequest): GetFriendProfileCardResponse {
|
||||
return GetFriendProfileCardResponse.newBuilder().apply {
|
||||
request.targetUinsList.forEach {
|
||||
ContactHelper.getProfileCard(it).getOrThrow().let { info ->
|
||||
addFriendsProfileCard(ProfileCard.newBuilder().apply {
|
||||
this.uin = info.uin.toLong()
|
||||
this.uid = ContactHelper.getUidByUinAsync(info.uin.toLong())
|
||||
this.nick = info.strNick
|
||||
this.remark = info.strReMark
|
||||
this.level = info.iQQLevel
|
||||
this.birthday = info.lBirthday
|
||||
this.loginDay = info.lLoginDays.toInt()
|
||||
this.voteCnt = info.lVoteCount.toInt()
|
||||
this.qid = info.qid ?: ""
|
||||
this.isSchoolVerified = info.schoolVerifiedFlag
|
||||
|
||||
this.ext = ExtInfo.newBuilder().apply {
|
||||
this.bigVip = info.bBigClubVipOpen == 1.toByte()
|
||||
this.hollywoodVip = info.bHollywoodVipOpen == 1.toByte()
|
||||
this.qqVip = info.bQQVipOpen == 1.toByte()
|
||||
this.superVip = info.bSuperQQOpen == 1.toByte()
|
||||
this.voted = info.bVoted == 1.toByte()
|
||||
}.build()
|
||||
}.build())
|
||||
}
|
||||
}
|
||||
request.targetUidsList.forEach {
|
||||
ContactHelper.getProfileCard(ContactHelper.getUinByUidAsync(it).toLong()).getOrThrow().let { info ->
|
||||
addFriendsProfileCard(ProfileCard.newBuilder().apply {
|
||||
this.uin = info.uin.toLong()
|
||||
this.uid = it
|
||||
this.nick = info.strNick
|
||||
this.remark = info.strReMark
|
||||
this.level = info.iQQLevel
|
||||
this.birthday = info.lBirthday
|
||||
this.loginDay = info.lLoginDays.toInt()
|
||||
this.voteCnt = info.lVoteCount.toInt()
|
||||
this.qid = info.qid ?: ""
|
||||
this.isSchoolVerified = info.schoolVerifiedFlag
|
||||
|
||||
this.ext = ExtInfo.newBuilder().apply {
|
||||
this.bigVip = info.bBigClubVipOpen == 1.toByte()
|
||||
this.hollywoodVip = info.bHollywoodVipOpen == 1.toByte()
|
||||
this.qqVip = info.bQQVipOpen == 1.toByte()
|
||||
this.superVip = info.bSuperQQOpen == 1.toByte()
|
||||
this.voted = info.bVoted == 1.toByte()
|
||||
}.build()
|
||||
}.build())
|
||||
}
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
@Grpc("FriendService", "GetStrangerProfileCard")
|
||||
override suspend fun getStrangerProfileCard(request: GetStrangerProfileCardRequest): GetStrangerProfileCardResponse {
|
||||
return GetStrangerProfileCardResponse.newBuilder().apply {
|
||||
request.targetUinsList.forEach {
|
||||
ContactHelper.refreshAndGetProfileCard(it).getOrThrow().let { info ->
|
||||
addStrangersProfileCard(ProfileCard.newBuilder().apply {
|
||||
this.uin = info.uin.toLong()
|
||||
this.uid = ContactHelper.getUidByUinAsync(info.uin.toLong())
|
||||
this.nick = info.strNick
|
||||
this.level = info.iQQLevel
|
||||
this.birthday = info.lBirthday
|
||||
this.loginDay = info.lLoginDays.toInt()
|
||||
this.voteCnt = info.lVoteCount.toInt()
|
||||
this.qid = info.qid ?: ""
|
||||
this.isSchoolVerified = info.schoolVerifiedFlag
|
||||
|
||||
this.ext = ExtInfo.newBuilder().apply {
|
||||
this.bigVip = info.bBigClubVipOpen == 1.toByte()
|
||||
this.hollywoodVip = info.bHollywoodVipOpen == 1.toByte()
|
||||
this.qqVip = info.bQQVipOpen == 1.toByte()
|
||||
this.superVip = info.bSuperQQOpen == 1.toByte()
|
||||
this.voted = info.bVoted == 1.toByte()
|
||||
}.build()
|
||||
}.build())
|
||||
}
|
||||
}
|
||||
request.targetUidsList.forEach {
|
||||
ContactHelper.refreshAndGetProfileCard(ContactHelper.getUinByUidAsync(it).toLong()).getOrThrow()
|
||||
.let { info ->
|
||||
addStrangersProfileCard(ProfileCard.newBuilder().apply {
|
||||
this.uin = info.uin.toLong()
|
||||
this.uid = it
|
||||
this.nick = info.strNick
|
||||
this.level = info.iQQLevel
|
||||
this.birthday = info.lBirthday
|
||||
this.loginDay = info.lLoginDays.toInt()
|
||||
this.voteCnt = info.lVoteCount.toInt()
|
||||
this.qid = info.qid ?: ""
|
||||
this.isSchoolVerified = info.schoolVerifiedFlag
|
||||
|
||||
this.ext = ExtInfo.newBuilder().apply {
|
||||
this.bigVip = info.bBigClubVipOpen == 1.toByte()
|
||||
this.hollywoodVip = info.bHollywoodVipOpen == 1.toByte()
|
||||
this.qqVip = info.bQQVipOpen == 1.toByte()
|
||||
this.superVip = info.bSuperQQOpen == 1.toByte()
|
||||
this.voted = info.bVoted == 1.toByte()
|
||||
}.build()
|
||||
}.build())
|
||||
}
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
@Grpc("FriendService", "SetProfileCard")
|
||||
override suspend fun setProfileCard(request: SetProfileCardRequest): SetProfileCardResponse {
|
||||
val bundle = Bundle()
|
||||
val service = QQInterfaces.app
|
||||
.getRuntimeService(IProfileProtocolService::class.java, "all")
|
||||
if (request.hasNick()) {
|
||||
bundle.putString(IProfileProtocolConst.KEY_NICK, request.nick)
|
||||
}
|
||||
if (request.hasCompany()) {
|
||||
bundle.putString(IProfileProtocolConst.KEY_COMPANY, request.company)
|
||||
}
|
||||
if (request.hasEmail()) {
|
||||
bundle.putString(IProfileProtocolConst.KEY_EMAIL, request.email)
|
||||
}
|
||||
if (request.hasCollege()) {
|
||||
bundle.putString(IProfileProtocolConst.KEY_COLLEGE, request.college)
|
||||
}
|
||||
if (request.hasPersonalNote()) {
|
||||
bundle.putString(IProfileProtocolConst.KEY_PERSONAL_NOTE, request.personalNote)
|
||||
}
|
||||
|
||||
if (request.hasBirthday()) {
|
||||
bundle.putInt(IProfileProtocolConst.KEY_BIRTHDAY, request.birthday)
|
||||
}
|
||||
if (request.hasAge()) {
|
||||
bundle.putInt(IProfileProtocolConst.KEY_AGE, request.age)
|
||||
}
|
||||
|
||||
service.setProfileDetail(bundle)
|
||||
return super.setProfileCard(request)
|
||||
}
|
||||
|
||||
@Grpc("FriendService", "IsBlackListUser")
|
||||
override suspend fun isBlackListUser(request: IsBlackListUserRequest): IsBlackListUserResponse {
|
||||
val uin = when (request.targetCase!!) {
|
||||
IsBlackListUserRequest.TargetCase.TARGET_UIN -> request.targetUin.toString()
|
||||
IsBlackListUserRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid)
|
||||
IsBlackListUserRequest.TargetCase.TARGET_NOT_SET -> throw StatusRuntimeException(
|
||||
Status.INVALID_ARGUMENT
|
||||
.withDescription("account not set")
|
||||
)
|
||||
}
|
||||
val blacklistApi = QRoute.api(IProfileCardBlacklistApi::class.java)
|
||||
val isBlack = withTimeoutOrNull(5000) {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
blacklistApi.isBlackOrBlackedUin(uin) {
|
||||
continuation.resume(it)
|
||||
}
|
||||
}
|
||||
} ?: false
|
||||
return IsBlackListUserResponse.newBuilder().setIsBlackListUser(isBlack).build()
|
||||
}
|
||||
|
||||
@Grpc("FriendService", "VoteUser")
|
||||
override suspend fun voteUser(request: VoteUserRequest): VoteUserResponse {
|
||||
ContactHelper.voteUser(
|
||||
when (request.targetCase!!) {
|
||||
VoteUserRequest.TargetCase.TARGET_UIN -> request.targetUin
|
||||
VoteUserRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong()
|
||||
VoteUserRequest.TargetCase.TARGET_NOT_SET -> throw StatusRuntimeException(
|
||||
Status.INVALID_ARGUMENT
|
||||
.withDescription("account not set")
|
||||
)
|
||||
}, request.voteCount
|
||||
).onFailure {
|
||||
throw StatusRuntimeException(
|
||||
Status.INTERNAL
|
||||
.withDescription(it.stackTraceToString())
|
||||
)
|
||||
}
|
||||
return VoteUserResponse.newBuilder().build()
|
||||
}
|
||||
|
||||
@Grpc("FriendService", "GetUidByUin")
|
||||
override suspend fun getUidByUin(request: GetUidByUinRequest): GetUidByUinResponse {
|
||||
return GetUidByUinResponse.newBuilder().apply {
|
||||
request.targetUinsList.forEach {
|
||||
putUidMap(it, ContactHelper.getUidByUinAsync(it))
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
@Grpc("FriendService", "GetUinByUid")
|
||||
override suspend fun getUinByUid(request: GetUinByUidRequest): GetUinByUidResponse {
|
||||
return GetUinByUidResponse.newBuilder().apply {
|
||||
request.targetUidsList.forEach {
|
||||
putUinMap(it, ContactHelper.getUinByUidAsync(it).toLong())
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ package kritor.service
|
||||
import io.grpc.Status
|
||||
import io.grpc.StatusRuntimeException
|
||||
import io.kritor.file.*
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToOidb
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||
import protobuf.auto.toByteArray
|
||||
@ -15,10 +16,9 @@ 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.cmd0x6d8.oidb_0x6d8
|
||||
import tencent.im.oidb.oidb_sso
|
||||
|
||||
internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCoroutineImplBase() {
|
||||
internal object GroupFileService : GroupFileServiceGrpcKt.GroupFileServiceCoroutineImplBase() {
|
||||
@Grpc("GroupFileService", "CreateFolder")
|
||||
override suspend fun createFolder(request: CreateFolderRequest): CreateFolderResponse {
|
||||
val data = Oidb0x6d7ReqBody(
|
||||
@ -31,42 +31,42 @@ internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCorouti
|
||||
).toByteArray()
|
||||
val fromServiceMsg = QQInterfaces.sendOidbAW("OidbSvc.0x6d7_0", 1751, 0, data)
|
||||
?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
|
||||
if (!fromServiceMsg.isSuccess) {
|
||||
if (fromServiceMsg.wupBuffer == null) {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
||||
}
|
||||
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
||||
oidbPkg.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
val oidbPkg = fromServiceMsg.decodeToOidb()
|
||||
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 {
|
||||
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.isSuccess) {
|
||||
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 oidbPkg = fromServiceMsg.decodeToOidb()
|
||||
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 { }
|
||||
return DeleteFolderResponse.newBuilder().build()
|
||||
}
|
||||
|
||||
@Grpc("GroupFileService", "DeleteFile")
|
||||
@ -82,57 +82,52 @@ internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCorouti
|
||||
}
|
||||
val fromServiceMsg = QQInterfaces.sendOidbAW("OidbSvc.0x6d6_3", 1750, 3, oidb0x6d6ReqBody.toByteArray())
|
||||
?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
|
||||
if (!fromServiceMsg.isSuccess) {
|
||||
if (fromServiceMsg.wupBuffer == null) {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
||||
}
|
||||
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
||||
oidbPkg.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
val oidbPkg = fromServiceMsg.decodeToOidb()
|
||||
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 { }
|
||||
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.isSuccess) {
|
||||
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 oidbPkg = fromServiceMsg.decodeToOidb()
|
||||
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 { }
|
||||
return RenameFolderResponse.newBuilder().build()
|
||||
}
|
||||
|
||||
@Grpc("GroupFileService", "GetFileSystemInfo")
|
||||
override suspend fun getFileSystemInfo(request: GetFileSystemInfoRequest): GetFileSystemInfoResponse {
|
||||
return getGroupFileSystemInfo(request.groupId)
|
||||
}
|
||||
|
||||
@Grpc("GroupFileService", "GetRootFiles")
|
||||
override suspend fun getRootFiles(request: GetRootFilesRequest): GetRootFilesResponse {
|
||||
return getRootFilesResponse {
|
||||
val response = GroupFileHelper.getGroupFiles(request.groupId)
|
||||
this.files.addAll(response.filesList)
|
||||
this.folders.addAll(response.foldersList)
|
||||
}
|
||||
}
|
||||
|
||||
@Grpc("GroupFileService", "GetFiles")
|
||||
override suspend fun getFiles(request: GetFilesRequest): GetFilesResponse {
|
||||
return GroupFileHelper.getGroupFiles(request.groupId, request.folderId)
|
||||
@Grpc("GroupFileService", "GetFileList")
|
||||
override suspend fun getFileList(request: GetFileListRequest): GetFileListResponse {
|
||||
return if (request.hasFolderId())
|
||||
GroupFileHelper.getGroupFiles(request.groupId, request.folderId)
|
||||
else
|
||||
GroupFileHelper.getGroupFiles(request.groupId)
|
||||
}
|
||||
}
|
@ -2,213 +2,188 @@ package kritor.service
|
||||
|
||||
import io.grpc.Status
|
||||
import io.grpc.StatusRuntimeException
|
||||
import io.kritor.group.BanMemberRequest
|
||||
import io.kritor.group.BanMemberResponse
|
||||
import io.kritor.group.GetGroupHonorRequest
|
||||
import io.kritor.group.GetGroupHonorResponse
|
||||
import io.kritor.group.GetGroupInfoRequest
|
||||
import io.kritor.group.GetGroupInfoResponse
|
||||
import io.kritor.group.GetGroupListRequest
|
||||
import io.kritor.group.GetGroupListResponse
|
||||
import io.kritor.group.GetGroupMemberInfoRequest
|
||||
import io.kritor.group.GetGroupMemberInfoResponse
|
||||
import io.kritor.group.GetGroupMemberListRequest
|
||||
import io.kritor.group.GetGroupMemberListResponse
|
||||
import io.kritor.group.GetNotJoinedGroupInfoRequest
|
||||
import io.kritor.group.GetNotJoinedGroupInfoResponse
|
||||
import io.kritor.group.GetProhibitedUserListRequest
|
||||
import io.kritor.group.GetProhibitedUserListResponse
|
||||
import io.kritor.group.GetRemainCountAtAllRequest
|
||||
import io.kritor.group.GetRemainCountAtAllResponse
|
||||
import io.kritor.group.GroupServiceGrpcKt
|
||||
import io.kritor.group.KickMemberRequest
|
||||
import io.kritor.group.KickMemberResponse
|
||||
import io.kritor.group.LeaveGroupRequest
|
||||
import io.kritor.group.LeaveGroupResponse
|
||||
import io.kritor.group.ModifyGroupNameRequest
|
||||
import io.kritor.group.ModifyGroupNameResponse
|
||||
import io.kritor.group.ModifyGroupRemarkRequest
|
||||
import io.kritor.group.ModifyGroupRemarkResponse
|
||||
import io.kritor.group.ModifyMemberCardRequest
|
||||
import io.kritor.group.ModifyMemberCardResponse
|
||||
import io.kritor.group.PokeMemberRequest
|
||||
import io.kritor.group.PokeMemberResponse
|
||||
import io.kritor.group.SetGroupAdminRequest
|
||||
import io.kritor.group.SetGroupAdminResponse
|
||||
import io.kritor.group.SetGroupUniqueTitleRequest
|
||||
import io.kritor.group.SetGroupUniqueTitleResponse
|
||||
import io.kritor.group.SetGroupWholeBanRequest
|
||||
import io.kritor.group.SetGroupWholeBanResponse
|
||||
import io.kritor.group.banMemberResponse
|
||||
import io.kritor.group.getGroupHonorResponse
|
||||
import io.kritor.group.getGroupInfoResponse
|
||||
import io.kritor.group.getGroupListResponse
|
||||
import io.kritor.group.getGroupMemberInfoResponse
|
||||
import io.kritor.group.getGroupMemberListResponse
|
||||
import io.kritor.group.getNotJoinedGroupInfoResponse
|
||||
import io.kritor.group.getProhibitedUserListResponse
|
||||
import io.kritor.group.getRemainCountAtAllResponse
|
||||
import io.kritor.group.groupHonorInfo
|
||||
import io.kritor.group.groupMemberInfo
|
||||
import io.kritor.group.kickMemberResponse
|
||||
import io.kritor.group.leaveGroupResponse
|
||||
import io.kritor.group.modifyGroupNameResponse
|
||||
import io.kritor.group.modifyGroupRemarkResponse
|
||||
import io.kritor.group.modifyMemberCardResponse
|
||||
import io.kritor.group.notJoinedGroupInfo
|
||||
import io.kritor.group.pokeMemberResponse
|
||||
import io.kritor.group.prohibitedUserInfo
|
||||
import io.kritor.group.setGroupAdminResponse
|
||||
import io.kritor.group.setGroupUniqueTitleResponse
|
||||
import io.kritor.group.setGroupWholeBanResponse
|
||||
import moe.fuqiuluo.shamrock.helper.TroopHonorHelper
|
||||
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
|
||||
import tencent.im.troop.honor.troop_honor
|
||||
|
||||
internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase() {
|
||||
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")
|
||||
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)
|
||||
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 {
|
||||
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 { }
|
||||
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")
|
||||
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 { }
|
||||
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 { }
|
||||
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")
|
||||
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 { }
|
||||
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")
|
||||
throw StatusRuntimeException(
|
||||
Status.PERMISSION_DENIED
|
||||
.withDescription("You are not admin of this group")
|
||||
)
|
||||
}
|
||||
|
||||
GroupHelper.modifyTroopName(request.groupId.toString(), request.groupName)
|
||||
|
||||
return modifyGroupNameResponse { }
|
||||
return ModifyGroupNameResponse.newBuilder().build()
|
||||
}
|
||||
|
||||
@Grpc("GroupService", "ModifyGroupRemark")
|
||||
override suspend fun modifyGroupRemark(request: ModifyGroupRemarkRequest): ModifyGroupRemarkResponse {
|
||||
GroupHelper.modifyGroupRemark(request.groupId, request.remark)
|
||||
|
||||
return modifyGroupRemarkResponse { }
|
||||
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")
|
||||
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)
|
||||
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 { }
|
||||
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")
|
||||
throw StatusRuntimeException(
|
||||
Status.PERMISSION_DENIED
|
||||
.withDescription("You are not admin of this group")
|
||||
)
|
||||
}
|
||||
|
||||
GroupHelper.setGroupUniqueTitle(request.groupId, 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")
|
||||
)
|
||||
}, request.uniqueTitle)
|
||||
GroupHelper.setGroupUniqueTitle(
|
||||
request.groupId.toString(), when (request.targetCase!!) {
|
||||
SetGroupUniqueTitleRequest.TargetCase.TARGET_UIN -> request.targetUin
|
||||
SetGroupUniqueTitleRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid)
|
||||
.toLong()
|
||||
|
||||
return setGroupUniqueTitleResponse { }
|
||||
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")
|
||||
throw StatusRuntimeException(
|
||||
Status.PERMISSION_DENIED
|
||||
.withDescription("You are not admin of this group")
|
||||
)
|
||||
}
|
||||
|
||||
GroupHelper.setGroupWholeBan(request.groupId, request.isBan)
|
||||
return setGroupWholeBanResponse { }
|
||||
return SetGroupWholeBanResponse.newBuilder().build()
|
||||
}
|
||||
|
||||
@Grpc("GroupService", "GetGroupInfo")
|
||||
@ -216,18 +191,20 @@ internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase()
|
||||
val groupInfo = GroupHelper.getGroupInfo(request.groupId.toString(), true).onFailure {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get group info").withCause(it))
|
||||
}.getOrThrow()
|
||||
return getGroupInfoResponse {
|
||||
this.groupInfo = io.kritor.group.groupInfo {
|
||||
return GetGroupInfoResponse.newBuilder().apply {
|
||||
this.groupInfo = GroupInfo.newBuilder().apply {
|
||||
groupId = groupInfo.troopcode.toLong()
|
||||
groupName = groupInfo.troopname.ifNullOrEmpty { groupInfo.troopRemark }.ifNullOrEmpty { groupInfo.newTroopName } ?: ""
|
||||
groupName =
|
||||
groupInfo.troopname.ifNullOrEmpty { groupInfo.troopRemark }.ifNullOrEmpty { groupInfo.newTroopName }
|
||||
?: ""
|
||||
groupRemark = groupInfo.troopRemark ?: ""
|
||||
owner = groupInfo.troopowneruin?.toLong() ?: 0
|
||||
admins.addAll(GroupHelper.getAdminList(groupId))
|
||||
addAllAdmins(GroupHelper.getAdminList(groupId))
|
||||
maxMemberCount = groupInfo.wMemberMax
|
||||
memberCount = groupInfo.wMemberNum
|
||||
groupUin = groupInfo.troopuin?.toLong() ?: 0
|
||||
}
|
||||
}
|
||||
}.build()
|
||||
}.build()
|
||||
}
|
||||
|
||||
@Grpc("GroupService", "GetGroupList")
|
||||
@ -235,36 +212,46 @@ internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase()
|
||||
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 {
|
||||
return GetGroupListResponse.newBuilder().apply {
|
||||
groupList.forEach { groupInfo ->
|
||||
this.groupInfo.add(io.kritor.group.groupInfo {
|
||||
groupId = groupInfo.troopcode.toLong()
|
||||
groupName = groupInfo.troopname.ifNullOrEmpty { groupInfo.troopRemark }.ifNullOrEmpty { groupInfo.newTroopName } ?: ""
|
||||
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
|
||||
admins.addAll(GroupHelper.getAdminList(groupId))
|
||||
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, when(request.targetCase!!) {
|
||||
GetGroupMemberInfoRequest.TargetCase.UIN -> request.uin
|
||||
GetGroupMemberInfoRequest.TargetCase.UID -> ContactHelper.getUinByUidAsync(request.uid).toLong()
|
||||
else -> throw StatusRuntimeException(Status.INVALID_ARGUMENT
|
||||
.withDescription("target not set")
|
||||
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)
|
||||
)
|
||||
}).onFailure {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get group member info").withCause(it))
|
||||
}.getOrThrow()
|
||||
return getGroupMemberInfoResponse {
|
||||
groupMemberInfo = groupMemberInfo {
|
||||
uid = if (request.targetCase == GetGroupMemberInfoRequest.TargetCase.UID) request.uid else ContactHelper.getUidByUinAsync(request.uin)
|
||||
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 }
|
||||
@ -272,73 +259,78 @@ internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase()
|
||||
.ifNullOrEmpty { memberInfo.friendnick } ?: ""
|
||||
age = memberInfo.age.toInt()
|
||||
uniqueTitle = memberInfo.mUniqueTitle ?: ""
|
||||
uniqueTitleExpireTime = memberInfo.mUniqueTitleExpire
|
||||
uniqueTitleExpireTime = memberInfo.mUniqueTitleExpire.toLong()
|
||||
card = memberInfo.troopnick.ifNullOrEmpty { memberInfo.friendnick } ?: ""
|
||||
joinTime = memberInfo.join_time
|
||||
lastActiveTime = memberInfo.last_active_time
|
||||
level = memberInfo.level
|
||||
shutUpTimestamp = memberInfo.gagTimeStamp
|
||||
shutUpTime = memberInfo.gagTimeStamp
|
||||
|
||||
distance = memberInfo.distance
|
||||
honor.addAll((memberInfo.honorList ?: "")
|
||||
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))
|
||||
val memberList = GroupHelper.getGroupMemberList(
|
||||
request.groupId,
|
||||
if (request.hasRefresh()) request.refresh else false
|
||||
).onFailure {
|
||||
throw StatusRuntimeException(
|
||||
Status.INTERNAL.withDescription("unable to get group member list").withCause(it)
|
||||
)
|
||||
}.getOrThrow()
|
||||
return getGroupMemberListResponse {
|
||||
memberList.forEach { memberInfo ->
|
||||
this.groupMemberInfo.add(groupMemberInfo {
|
||||
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
|
||||
return GetGroupMemberListResponse.newBuilder().apply {
|
||||
memberList.values.forEach { memberInfo ->
|
||||
this.addGroupMembersInfo(GroupMemberInfo.newBuilder().apply {
|
||||
uid = memberInfo.uid
|
||||
uin = memberInfo.uin
|
||||
nick = memberInfo.nick ?: ""
|
||||
age = 0
|
||||
uniqueTitle = memberInfo.memberSpecialTitle ?: ""
|
||||
uniqueTitleExpireTime = memberInfo.specialTitleExpireTime
|
||||
card = memberInfo.cardName.ifNullOrEmpty { memberInfo.nick } ?: ""
|
||||
joinTime = memberInfo.joinTime.toLong()
|
||||
lastActiveTime = memberInfo.lastSpeakTime.toLong()
|
||||
level = memberInfo.memberLevel
|
||||
shutUpTime = memberInfo.shutUpTime.toLong()
|
||||
|
||||
distance = memberInfo.distance
|
||||
honor.addAll((memberInfo.honorList ?: "")
|
||||
.split("|")
|
||||
.filter { it.isNotBlank() }
|
||||
.map { it.toInt() })
|
||||
distance = 0
|
||||
addAllHonors(memberInfo.groupHonor.let { bytes ->
|
||||
val honor = troop_honor.GroupUserCardHonor()
|
||||
honor.mergeFrom(bytes)
|
||||
honor.id.get()
|
||||
})
|
||||
unfriendly = false
|
||||
cardChangeable = GroupHelper.isAdmin(request.groupId.toString())
|
||||
cardChangeable = memberInfo.role == com.tencent.qqnt.kernelpublic.nativeinterface.MemberRole.ADMIN
|
||||
})
|
||||
}
|
||||
}
|
||||
}.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))
|
||||
throw StatusRuntimeException(
|
||||
Status.INTERNAL.withDescription("unable to get prohibited user list").withCause(it)
|
||||
)
|
||||
}.getOrThrow()
|
||||
return getProhibitedUserListResponse {
|
||||
return GetProhibitedUserListResponse.newBuilder().apply {
|
||||
prohibitedList.forEach {
|
||||
this.prohibitedUserInfo.add(prohibitedUserInfo {
|
||||
this.addProhibitedUsersInfo(ProhibitedUserInfo.newBuilder().apply {
|
||||
uid = ContactHelper.getUidByUinAsync(it.memberUin)
|
||||
uin = it.memberUin
|
||||
prohibitedTime = it.shutuptimestap
|
||||
prohibitedTime = it.shutuptimestap.toLong()
|
||||
})
|
||||
}
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
@Grpc("GroupService", "GetRemainCountAtAll")
|
||||
@ -346,52 +338,57 @@ internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase()
|
||||
val remainAtAllRsp = GroupHelper.getGroupRemainAtAllRemain(request.groupId).onFailure {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get remain count").withCause(it))
|
||||
}.getOrThrow()
|
||||
return getRemainCountAtAllResponse {
|
||||
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))
|
||||
throw StatusRuntimeException(
|
||||
Status.INTERNAL.withDescription("unable to get not joined group info").withCause(it)
|
||||
)
|
||||
}.getOrThrow()
|
||||
return getNotJoinedGroupInfoResponse {
|
||||
this.groupInfo = notJoinedGroupInfo {
|
||||
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()
|
||||
createTime = groupInfo.createTime
|
||||
groupFlag = groupInfo.groupFlag
|
||||
groupFlagExt = groupInfo.groupFlagExt
|
||||
}
|
||||
}
|
||||
}.build()
|
||||
}.build()
|
||||
}
|
||||
|
||||
@Grpc("GroupService", "GetGroupHonor")
|
||||
override suspend fun getGroupHonor(request: GetGroupHonorRequest): GetGroupHonorResponse {
|
||||
return getGroupHonorResponse {
|
||||
GroupHelper.getGroupMemberList(request.groupId.toString(), true).onFailure {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get group member list").withCause(it))
|
||||
return GetGroupHonorResponse.newBuilder().apply {
|
||||
GroupHelper.getGroupMemberList(request.groupId, 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)
|
||||
memberList.values.forEach { member ->
|
||||
member.groupHonor.let { bytes ->
|
||||
val honor = troop_honor.GroupUserCardHonor()
|
||||
honor.mergeFrom(bytes)
|
||||
honor.id.get()
|
||||
}.forEach {
|
||||
val honor = decodeHonor(member.uin, it, 0)
|
||||
if (honor != null) {
|
||||
groupHonorInfo.add(groupHonorInfo {
|
||||
uid = ContactHelper.getUidByUinAsync(member.memberuin.toLong())
|
||||
uin = member.memberuin.toLong()
|
||||
nick = member.troopnick
|
||||
.ifNullOrEmpty { member.hwName }
|
||||
.ifNullOrEmpty { member.troopColorNick }
|
||||
.ifNullOrEmpty { member.friendnick } ?: ""
|
||||
addGroupHonorsInfo(GroupHonorInfo.newBuilder().apply {
|
||||
uid = member.uid
|
||||
uin = member.uin
|
||||
nick = member.nick.ifEmpty {
|
||||
member.cardName
|
||||
} ?: ""
|
||||
honorName = honor.honorName
|
||||
avatar = honor.honorIconUrl
|
||||
id = honor.honorId
|
||||
@ -401,6 +398,6 @@ internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
}
|
@ -1,7 +1,475 @@
|
||||
package kritor.service
|
||||
|
||||
import io.kritor.message.MessageServiceGrpcKt
|
||||
import com.tencent.mobileqq.qroute.QRoute
|
||||
import com.tencent.qqnt.kernelpublic.nativeinterface.Contact
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
|
||||
import com.tencent.qqnt.msg.api.IMsgService
|
||||
import io.grpc.Status
|
||||
import io.grpc.StatusRuntimeException
|
||||
import io.kritor.common.*
|
||||
import io.kritor.message.*
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import protobuf.auto.toByteArray
|
||||
import protobuf.message.*
|
||||
import protobuf.message.element.GeneralFlags
|
||||
import protobuf.message.routing.C2C
|
||||
import protobuf.message.routing.Grp
|
||||
import qq.service.QQInterfaces
|
||||
import qq.service.contact.longPeer
|
||||
import qq.service.internals.NTServiceFetcher
|
||||
import qq.service.msg.*
|
||||
import qq.service.msg.ForwardMessageHelper
|
||||
import qq.service.msg.MessageHelper
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.random.Random
|
||||
import kotlin.random.nextUInt
|
||||
|
||||
internal object MessageService: MessageServiceGrpcKt.MessageServiceCoroutineImplBase() {
|
||||
internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImplBase() {
|
||||
@Grpc("MessageService", "SendMessage")
|
||||
override suspend fun sendMessage(request: SendMessageRequest): SendMessageResponse {
|
||||
val contact = request.contact.let {
|
||||
MessageHelper.generateContact(
|
||||
when (it.scene!!) {
|
||||
Scene.GROUP -> MsgConstant.KCHATTYPEGROUP
|
||||
Scene.FRIEND -> MsgConstant.KCHATTYPEC2C
|
||||
Scene.GUILD -> MsgConstant.KCHATTYPEGUILD
|
||||
Scene.STRANGER_FROM_GROUP -> MsgConstant.KCHATTYPETEMPC2CFROMGROUP
|
||||
Scene.NEARBY -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
|
||||
Scene.STRANGER -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
|
||||
Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene"))
|
||||
}, it.peer, it.subPeer
|
||||
)
|
||||
}
|
||||
|
||||
val uniseq = MessageHelper.generateMsgId(contact.chatType)
|
||||
return SendMessageResponse.newBuilder().apply {
|
||||
this.messageId = MessageHelper.sendMessage(
|
||||
contact,
|
||||
NtMsgConvertor.convertToNtMsgs(contact, uniseq, request.elementsList),
|
||||
request.retryCount,
|
||||
uniseq
|
||||
).onFailure {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withCause(it))
|
||||
}.getOrThrow().toString()
|
||||
}.build()
|
||||
}
|
||||
|
||||
@Grpc("MessageService", "SendMessageByResId")
|
||||
override suspend fun sendMessageByResId(request: SendMessageByResIdRequest): SendMessageByResIdResponse {
|
||||
val contact = request.contact
|
||||
val req = PbSendMsgReq(
|
||||
routingHead = when (request.contact.scene) {
|
||||
Scene.GROUP -> RoutingHead(grp = Grp(contact.longPeer().toUInt()))
|
||||
Scene.FRIEND -> RoutingHead(c2c = C2C(contact.longPeer().toUInt()))
|
||||
else -> RoutingHead(grp = Grp(contact.longPeer().toUInt()))
|
||||
},
|
||||
contentHead = ContentHead(1, 0, 0, 0),
|
||||
msgBody = MsgBody(
|
||||
richText = RichText(
|
||||
elements = arrayListOf(
|
||||
Elem(
|
||||
generalFlags = GeneralFlags(
|
||||
longTextFlag = 1u,
|
||||
longTextResid = request.resId
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
msgSeq = Random.nextUInt(),
|
||||
msgRand = Random.nextUInt(),
|
||||
msgVia = 0u
|
||||
)
|
||||
QQInterfaces.sendBuffer("MessageSvc.PbSendMsg", true, req.toByteArray())
|
||||
return SendMessageByResIdResponse.newBuilder().build()
|
||||
}
|
||||
|
||||
@Grpc("MessageService", "SetMessageReaded")
|
||||
override suspend fun setMessageReaded(request: SetMessageReadRequest): SetMessageReadResponse {
|
||||
val contact = request.contact
|
||||
val kernelService = NTServiceFetcher.kernelService
|
||||
val sessionService = kernelService.wrapperSession
|
||||
val service = sessionService.msgService
|
||||
val chatType = when (contact.scene!!) {
|
||||
Scene.GROUP -> MsgConstant.KCHATTYPEGROUP
|
||||
Scene.FRIEND -> MsgConstant.KCHATTYPEC2C
|
||||
Scene.GUILD -> MsgConstant.KCHATTYPEGUILD
|
||||
Scene.STRANGER_FROM_GROUP -> MsgConstant.KCHATTYPETEMPC2CFROMGROUP
|
||||
Scene.NEARBY -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
|
||||
Scene.STRANGER -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
|
||||
Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene"))
|
||||
}
|
||||
service.clearMsgRecords(
|
||||
Contact(
|
||||
chatType,
|
||||
contact.peer,
|
||||
contact.subPeer
|
||||
), null)
|
||||
return SetMessageReadResponse.newBuilder().build()
|
||||
}
|
||||
|
||||
@Grpc("MessageService", "RecallMessage")
|
||||
override suspend fun recallMessage(request: RecallMessageRequest): RecallMessageResponse {
|
||||
val contact = request.contact.let {
|
||||
MessageHelper.generateContact(
|
||||
when (it.scene!!) {
|
||||
Scene.GROUP -> MsgConstant.KCHATTYPEGROUP
|
||||
Scene.FRIEND -> MsgConstant.KCHATTYPEC2C
|
||||
Scene.GUILD -> MsgConstant.KCHATTYPEGUILD
|
||||
Scene.STRANGER_FROM_GROUP -> MsgConstant.KCHATTYPETEMPC2CFROMGROUP
|
||||
Scene.NEARBY -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
|
||||
Scene.STRANGER -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
|
||||
Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene"))
|
||||
}, it.peer, it.subPeer
|
||||
)
|
||||
}
|
||||
val kernelService = NTServiceFetcher.kernelService
|
||||
val sessionService = kernelService.wrapperSession
|
||||
val service = sessionService.msgService
|
||||
service.recallMsg(contact, arrayListOf(request.messageId.toLong())) { code, msg ->
|
||||
if (code != 0) {
|
||||
LogCenter.log("消息撤回失败: $code:$msg", Level.WARN)
|
||||
}
|
||||
}
|
||||
|
||||
return RecallMessageResponse.newBuilder().build()
|
||||
}
|
||||
|
||||
@Grpc("MessageService", "GetMessage")
|
||||
override suspend fun getMessage(request: GetMessageRequest): GetMessageResponse {
|
||||
val contact = request.contact.let {
|
||||
MessageHelper.generateContact(
|
||||
when (it.scene!!) {
|
||||
Scene.GROUP -> MsgConstant.KCHATTYPEGROUP
|
||||
Scene.FRIEND -> MsgConstant.KCHATTYPEC2C
|
||||
Scene.GUILD -> MsgConstant.KCHATTYPEGUILD
|
||||
Scene.STRANGER_FROM_GROUP -> MsgConstant.KCHATTYPETEMPC2CFROMGROUP
|
||||
Scene.NEARBY -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
|
||||
Scene.STRANGER -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
|
||||
Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene"))
|
||||
}, it.peer, it.subPeer
|
||||
)
|
||||
}
|
||||
val msg: MsgRecord = withTimeoutOrNull(5000) {
|
||||
val service = QRoute.api(IMsgService::class.java)
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
service.getMsgsByMsgId(contact, arrayListOf(request.messageId.toLong())) { code, _, msgRecords ->
|
||||
if (code == 0 && msgRecords.isNotEmpty()) {
|
||||
continuation.resume(msgRecords.first())
|
||||
} else {
|
||||
continuation.resume(null)
|
||||
}
|
||||
}
|
||||
continuation.invokeOnCancellation {
|
||||
continuation.resume(null)
|
||||
}
|
||||
}
|
||||
} ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Message not found"))
|
||||
|
||||
return GetMessageResponse.newBuilder().apply {
|
||||
this.message = PushMessageBody.newBuilder().apply {
|
||||
this.messageId = msg.msgId.toString()
|
||||
this.contact = request.contact
|
||||
this.sender = Sender.newBuilder().apply {
|
||||
this.uid = msg.senderUid ?: ""
|
||||
this.uin = msg.senderUin
|
||||
this.nick = msg.sendNickName ?: ""
|
||||
}.build()
|
||||
this.messageSeq = msg.msgSeq
|
||||
this.addAllElements(msg.elements.toKritorReqMessages(contact))
|
||||
}.build()
|
||||
}.build()
|
||||
}
|
||||
|
||||
@Grpc("MessageService", "GetMessageBySeq")
|
||||
override suspend fun getMessageBySeq(request: GetMessageBySeqRequest): GetMessageBySeqResponse {
|
||||
val contact = request.contact.let {
|
||||
MessageHelper.generateContact(
|
||||
when (it.scene!!) {
|
||||
Scene.GROUP -> MsgConstant.KCHATTYPEGROUP
|
||||
Scene.FRIEND -> MsgConstant.KCHATTYPEC2C
|
||||
Scene.GUILD -> MsgConstant.KCHATTYPEGUILD
|
||||
Scene.STRANGER_FROM_GROUP -> MsgConstant.KCHATTYPETEMPC2CFROMGROUP
|
||||
Scene.NEARBY -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
|
||||
Scene.STRANGER -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
|
||||
Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene"))
|
||||
}, it.peer, it.subPeer
|
||||
)
|
||||
}
|
||||
val msg: MsgRecord = withTimeoutOrNull(5000) {
|
||||
val service = QRoute.api(IMsgService::class.java)
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
service.getMsgsBySeqAndCount(contact, request.messageSeq, 1, true) { code, _, msgRecords ->
|
||||
if (code == 0 && msgRecords.isNotEmpty()) {
|
||||
continuation.resume(msgRecords.first())
|
||||
} else {
|
||||
continuation.resume(null)
|
||||
}
|
||||
}
|
||||
continuation.invokeOnCancellation {
|
||||
continuation.resume(null)
|
||||
}
|
||||
}
|
||||
} ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Message not found"))
|
||||
|
||||
return GetMessageBySeqResponse.newBuilder().apply {
|
||||
this.message = PushMessageBody.newBuilder().apply {
|
||||
this.messageId = msg.msgId.toString()
|
||||
this.contact = request.contact
|
||||
this.sender = Sender.newBuilder().apply {
|
||||
this.uin = msg.senderUin
|
||||
this.nick = msg.sendNickName ?: ""
|
||||
this.uid = msg.senderUid ?: ""
|
||||
}.build()
|
||||
this.messageSeq = msg.msgSeq
|
||||
this.addAllElements(msg.elements.toKritorReqMessages(contact))
|
||||
}.build()
|
||||
}.build()
|
||||
}
|
||||
|
||||
@Grpc("MessageService", "GetHistoryMessage")
|
||||
override suspend fun getHistoryMessage(request: GetHistoryMessageRequest): GetHistoryMessageResponse {
|
||||
val contact = request.contact.let {
|
||||
MessageHelper.generateContact(
|
||||
when (it.scene!!) {
|
||||
Scene.GROUP -> MsgConstant.KCHATTYPEGROUP
|
||||
Scene.FRIEND -> MsgConstant.KCHATTYPEC2C
|
||||
Scene.GUILD -> MsgConstant.KCHATTYPEGUILD
|
||||
Scene.STRANGER_FROM_GROUP -> MsgConstant.KCHATTYPETEMPC2CFROMGROUP
|
||||
Scene.NEARBY -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
|
||||
Scene.STRANGER -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
|
||||
Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene"))
|
||||
}, it.peer, it.subPeer
|
||||
)
|
||||
}
|
||||
val msgs: List<MsgRecord> = withTimeoutOrNull(5000) {
|
||||
val service = QRoute.api(IMsgService::class.java)
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
service.getMsgs(contact, request.startMessageId.toLong(), request.count, true) { code, _, msgRecords ->
|
||||
if (code == 0 && msgRecords.isNotEmpty()) {
|
||||
continuation.resume(msgRecords)
|
||||
} else {
|
||||
continuation.resume(null)
|
||||
}
|
||||
}
|
||||
continuation.invokeOnCancellation {
|
||||
continuation.resume(null)
|
||||
}
|
||||
}
|
||||
} ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Messages not found"))
|
||||
|
||||
return GetHistoryMessageResponse.newBuilder().apply {
|
||||
msgs.forEach {
|
||||
addMessages(PushMessageBody.newBuilder().apply {
|
||||
this.messageId = it.msgId.toString()
|
||||
this.contact = request.contact
|
||||
this.sender = Sender.newBuilder().apply {
|
||||
this.uin = it.senderUin
|
||||
this.nick = it.sendNickName ?: ""
|
||||
this.uid = it.senderUid ?: ""
|
||||
}.build()
|
||||
this.messageSeq = it.msgSeq
|
||||
this.addAllElements(it.elements.toKritorReqMessages(contact))
|
||||
})
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
@Grpc("MessageService", "UploadForwardMessage")
|
||||
override suspend fun uploadForwardMessage(request: UploadForwardMessageRequest): UploadForwardMessageResponse {
|
||||
val contact = request.contact.let {
|
||||
MessageHelper.generateContact(
|
||||
when (it.scene!!) {
|
||||
Scene.GROUP -> MsgConstant.KCHATTYPEGROUP
|
||||
Scene.FRIEND -> MsgConstant.KCHATTYPEC2C
|
||||
Scene.GUILD -> MsgConstant.KCHATTYPEGUILD
|
||||
Scene.STRANGER_FROM_GROUP -> MsgConstant.KCHATTYPETEMPC2CFROMGROUP
|
||||
Scene.NEARBY -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
|
||||
Scene.STRANGER -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
|
||||
Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene"))
|
||||
}, it.peer, it.subPeer
|
||||
)
|
||||
}
|
||||
|
||||
val forwardMessage = ForwardMessageHelper.uploadMultiMsg(
|
||||
contact,
|
||||
request.messagesList
|
||||
).onFailure {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withCause(it))
|
||||
}.getOrThrow()
|
||||
|
||||
return UploadForwardMessageResponse.newBuilder().apply {
|
||||
this.resId = forwardMessage.resId
|
||||
}.build()
|
||||
}
|
||||
|
||||
@Grpc("MessageService", "DownloadForwardMessage")
|
||||
override suspend fun downloadForwardMessage(request: DownloadForwardMessageRequest): DownloadForwardMessageResponse {
|
||||
return DownloadForwardMessageResponse.newBuilder().apply {
|
||||
this.addAllMessages(
|
||||
MessageHelper.getForwardMsg(request.resId).onFailure {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withCause(it))
|
||||
}.getOrThrow().map { detail ->
|
||||
PushMessageBody.newBuilder().apply {
|
||||
this.time = detail.time.toLong()
|
||||
this.messageId = detail.qqMsgId.toString()
|
||||
this.messageSeq = detail.msgSeq
|
||||
this.contact = io.kritor.common.Contact.newBuilder().apply {
|
||||
this.scene = when (detail.msgType) {
|
||||
MsgConstant.KCHATTYPEC2C -> Scene.FRIEND
|
||||
MsgConstant.KCHATTYPEGROUP -> Scene.GROUP
|
||||
MsgConstant.KCHATTYPEGUILD -> Scene.GUILD
|
||||
MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> Scene.STRANGER_FROM_GROUP
|
||||
MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN -> Scene.NEARBY
|
||||
else -> Scene.STRANGER
|
||||
}
|
||||
this.peer = detail.peerId.toString()
|
||||
}.build()
|
||||
this.sender = Sender.newBuilder().apply {
|
||||
this.uin = detail.sender.userId
|
||||
this.nick = detail.sender.nickName
|
||||
this.uid = detail.sender.uid
|
||||
}.build()
|
||||
detail.message?.elements?.toKritorResponseMessages(
|
||||
Contact(
|
||||
detail.msgType,
|
||||
detail.peerId.toString(),
|
||||
null
|
||||
)
|
||||
)?.let {
|
||||
this.addAllElements(it)
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
)
|
||||
}.build()
|
||||
}
|
||||
|
||||
@Grpc("MessageService", "DeleteEssenceMessage")
|
||||
override suspend fun deleteEssenceMessage(request: DeleteEssenceMessageRequest): DeleteEssenceMessageResponse {
|
||||
val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, request.groupId.toString())
|
||||
val msg: MsgRecord = withTimeoutOrNull(5000) {
|
||||
val service = QRoute.api(IMsgService::class.java)
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
service.getMsgsByMsgId(contact, arrayListOf(request.messageId.toLong())) { code, _, msgRecords ->
|
||||
if (code == 0 && msgRecords.isNotEmpty()) {
|
||||
continuation.resume(msgRecords.first())
|
||||
} else {
|
||||
continuation.resume(null)
|
||||
}
|
||||
}
|
||||
continuation.invokeOnCancellation {
|
||||
continuation.resume(null)
|
||||
}
|
||||
}
|
||||
} ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Message not found"))
|
||||
if (MessageHelper.deleteEssenceMessage(request.groupId, msg.msgSeq, msg.msgRandom) == null)
|
||||
throw StatusRuntimeException(Status.NOT_FOUND.withDescription("delete essence message failed"))
|
||||
return DeleteEssenceMessageResponse.newBuilder().build()
|
||||
}
|
||||
|
||||
@Grpc("MessageService", "GetEssenceMessageList")
|
||||
override suspend fun getEssenceMessageList(request: GetEssenceMessageListRequest): GetEssenceMessageListResponse {
|
||||
val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, request.groupId.toString())
|
||||
return GetEssenceMessageListResponse.newBuilder().apply {
|
||||
MessageHelper.getEssenceMessageList(request.groupId, request.page, request.pageSize).onFailure {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withCause(it))
|
||||
}.getOrThrow().forEach {
|
||||
addMessages(EssenceMessageBody.newBuilder().apply {
|
||||
withTimeoutOrNull(5000) {
|
||||
val service = QRoute.api(IMsgService::class.java)
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
service.getMsgsBySeqAndCount(contact, it.messageSeq, 1, true) { code, _, msgRecords ->
|
||||
if (code == 0 && msgRecords.isNotEmpty()) {
|
||||
continuation.resume(msgRecords.first())
|
||||
} else {
|
||||
continuation.resume(null)
|
||||
}
|
||||
}
|
||||
continuation.invokeOnCancellation {
|
||||
continuation.resume(null)
|
||||
}
|
||||
}
|
||||
}?.let {
|
||||
this.messageId = it.msgId.toString()
|
||||
}
|
||||
this.messageSeq = it.messageSeq
|
||||
this.messageTime = it.senderTime
|
||||
this.senderNick = it.senderNick
|
||||
this.senderUin = it.senderId
|
||||
this.operationTime = it.operatorTime
|
||||
this.operatorNick = it.operatorNick
|
||||
this.operatorUin = it.operatorId
|
||||
this.jsonElements = it.messageContent.toString()
|
||||
})
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
@Grpc("MessageService", "SetEssenceMessage")
|
||||
override suspend fun setEssenceMessage(request: SetEssenceMessageRequest): SetEssenceMessageResponse {
|
||||
val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, request.groupId.toString())
|
||||
val msg: MsgRecord = withTimeoutOrNull(5000) {
|
||||
val service = QRoute.api(IMsgService::class.java)
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
service.getMsgsByMsgId(contact, arrayListOf(request.messageId.toLong())) { code, _, msgRecords ->
|
||||
if (code == 0 && msgRecords.isNotEmpty()) {
|
||||
continuation.resume(msgRecords.first())
|
||||
} else {
|
||||
continuation.resume(null)
|
||||
}
|
||||
}
|
||||
continuation.invokeOnCancellation {
|
||||
continuation.resume(null)
|
||||
}
|
||||
}
|
||||
} ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Message not found"))
|
||||
if (MessageHelper.setEssenceMessage(request.groupId, msg.msgSeq, msg.msgRandom) == null) {
|
||||
throw StatusRuntimeException(Status.NOT_FOUND.withDescription("set essence message failed"))
|
||||
}
|
||||
return SetEssenceMessageResponse.newBuilder().build()
|
||||
}
|
||||
|
||||
@Grpc("MessageService", "ReactMessageWithEmoji")
|
||||
override suspend fun reactMessageWithEmoji(request: ReactMessageWithEmojiRequest): ReactMessageWithEmojiResponse {
|
||||
val contact = request.contact.let {
|
||||
MessageHelper.generateContact(
|
||||
when (it.scene!!) {
|
||||
Scene.GROUP -> MsgConstant.KCHATTYPEGROUP
|
||||
Scene.FRIEND -> MsgConstant.KCHATTYPEC2C
|
||||
Scene.GUILD -> MsgConstant.KCHATTYPEGUILD
|
||||
Scene.STRANGER_FROM_GROUP -> MsgConstant.KCHATTYPETEMPC2CFROMGROUP
|
||||
Scene.NEARBY -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
|
||||
Scene.STRANGER -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
|
||||
Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene"))
|
||||
}, it.peer, it.subPeer
|
||||
)
|
||||
}
|
||||
val msg: MsgRecord = withTimeoutOrNull(5000) {
|
||||
val service = QRoute.api(IMsgService::class.java)
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
service.getMsgsByMsgId(contact, arrayListOf(request.messageId.toLong())) { code, _, msgRecords ->
|
||||
if (code == 0 && msgRecords.isNotEmpty()) {
|
||||
continuation.resume(msgRecords.first())
|
||||
} else {
|
||||
continuation.resume(null)
|
||||
}
|
||||
}
|
||||
continuation.invokeOnCancellation {
|
||||
continuation.resume(null)
|
||||
}
|
||||
}
|
||||
} ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Message not found"))
|
||||
MessageHelper.setGroupMessageCommentFace(
|
||||
request.contact.longPeer(),
|
||||
msg.msgSeq.toULong(),
|
||||
request.faceId.toString(),
|
||||
request.isSet
|
||||
)
|
||||
return ReactMessageWithEmojiResponse.newBuilder().build()
|
||||
}
|
||||
}
|
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()
|
||||
}
|
||||
}
|
58
xposed/src/main/java/kritor/service/WebService.kt
Normal file
58
xposed/src/main/java/kritor/service/WebService.kt
Normal file
@ -0,0 +1,58 @@
|
||||
package kritor.service
|
||||
|
||||
import io.grpc.Status
|
||||
import io.grpc.StatusRuntimeException
|
||||
import io.kritor.web.*
|
||||
import qq.service.ticket.TicketHelper
|
||||
|
||||
internal object WebService: WebServiceGrpcKt.WebServiceCoroutineImplBase() {
|
||||
@Grpc("WebService", "GetCookies")
|
||||
override suspend fun getCookies(request: GetCookiesRequest): GetCookiesResponse {
|
||||
return GetCookiesResponse.newBuilder().apply {
|
||||
if (request.domain.isNullOrEmpty()) {
|
||||
this.cookie = TicketHelper.getCookie()
|
||||
} else {
|
||||
this.cookie = TicketHelper.getCookie(request.domain)
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
@Grpc("WebService", "GetCredentials")
|
||||
override suspend fun getCredentials(request: GetCredentialsRequest): GetCredentialsResponse {
|
||||
return GetCredentialsResponse.newBuilder().apply {
|
||||
if (request.domain.isNullOrEmpty()) {
|
||||
val uin = TicketHelper.getUin()
|
||||
val skey = TicketHelper.getRealSkey(uin)
|
||||
val pskey = TicketHelper.getPSKey(uin)
|
||||
this.cookie = "o_cookie=$uin; ied_qq=o$uin; pac_uid=1_$uin; uin=o$uin; skey=$skey; p_uin=o$uin; p_skey=$pskey;"
|
||||
this.bkn = TicketHelper.getCSRF(pskey)
|
||||
} else {
|
||||
val uin = TicketHelper.getUin()
|
||||
val skey = TicketHelper.getRealSkey(uin)
|
||||
val pskey = TicketHelper.getPSKey(uin, request.domain) ?: ""
|
||||
val pt4token = TicketHelper.getPt4Token(uin, request.domain) ?: ""
|
||||
this.cookie = "o_cookie=$uin; ied_qq=o$uin; pac_uid=1_$uin; uin=o$uin; skey=$skey; p_uin=o$uin; p_skey=$pskey; pt4_token=$pt4token;"
|
||||
this.bkn = TicketHelper.getCSRF(pskey)
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
@Grpc("WebService", "GetCSRFToken")
|
||||
override suspend fun getCSRFToken(request: GetCSRFTokenRequest): GetCSRFTokenResponse {
|
||||
return GetCSRFTokenResponse.newBuilder().apply {
|
||||
if (request.domain.isNullOrEmpty()) {
|
||||
this.bkn = TicketHelper.getCSRF()
|
||||
} else {
|
||||
this.bkn = TicketHelper.getCSRF(TicketHelper.getUin(), request.domain)
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
@Grpc("WebService", "GetHttpCookies")
|
||||
override suspend fun getHttpCookies(request: GetHttpCookiesRequest): GetHttpCookiesResponse {
|
||||
return GetHttpCookiesResponse.newBuilder().apply {
|
||||
this.cookie = TicketHelper.getHttpCookies(request.appid, request.daid, request.jumpUrl)
|
||||
?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to get http cookies"))
|
||||
}.build()
|
||||
}
|
||||
}
|
@ -2,5 +2,5 @@ package moe.fuqiuluo.shamrock.config
|
||||
|
||||
object EnableOldBDH: ConfigKey<Boolean>() {
|
||||
override fun name() = "enable_old_bdh"
|
||||
override fun default() = false
|
||||
override fun default() = true
|
||||
}
|
@ -20,6 +20,7 @@ private val configKeys = setOf(
|
||||
ResourceGroup,
|
||||
RPCAddress,
|
||||
RPCPort,
|
||||
AliveReply,
|
||||
)
|
||||
|
||||
internal object ShamrockConfig: Properties() {
|
||||
|
@ -14,7 +14,10 @@ import moe.fuqiuluo.shamrock.tools.toast
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.AppTalker
|
||||
import mqq.app.MobileQQ
|
||||
import java.io.File
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
|
||||
internal enum class Level(
|
||||
val id: Byte
|
||||
@ -31,7 +34,29 @@ internal object LogCenter {
|
||||
// 格式化时间
|
||||
SimpleDateFormat("yyyy-MM-dd").format(Date())
|
||||
}_"
|
||||
private val LogFile = MobileQQ.getContext().getExternalFilesDir(null)!!
|
||||
private var LogFile = generateLogFile()
|
||||
|
||||
private val format = SimpleDateFormat("[HH:mm:ss] ")
|
||||
private val timer = Timer()
|
||||
|
||||
init {
|
||||
val now = Calendar.getInstance()
|
||||
val tomorrowMidnight = Calendar.getInstance().apply {
|
||||
add(Calendar.DAY_OF_YEAR, 1)
|
||||
set(Calendar.HOUR_OF_DAY, 0)
|
||||
set(Calendar.MINUTE, 0)
|
||||
set(Calendar.SECOND, 0)
|
||||
set(Calendar.MILLISECOND, 0)
|
||||
}
|
||||
val delay = tomorrowMidnight.timeInMillis - now.timeInMillis
|
||||
timer.scheduleAtFixedRate(object : TimerTask() {
|
||||
override fun run() {
|
||||
LogFile = generateLogFile()
|
||||
}
|
||||
}, delay, 24 * 60 * 60 * 1000)
|
||||
}
|
||||
|
||||
private fun generateLogFile() = MobileQQ.getContext().getExternalFilesDir(null)!!
|
||||
.parentFile!!.resolve("Tencent/Shamrock/log").also {
|
||||
if (it.exists()) it.delete()
|
||||
it.mkdirs()
|
||||
@ -49,8 +74,6 @@ internal object LogCenter {
|
||||
return@let result
|
||||
}
|
||||
|
||||
private val format = SimpleDateFormat("[HH:mm:ss] ")
|
||||
|
||||
fun log(string: String, level: Level = Level.INFO, toast: Boolean = false) {
|
||||
if (!ShamrockConfig[DebugMode] && level == Level.DEBUG) {
|
||||
return
|
||||
|
@ -1,57 +1,62 @@
|
||||
@file:OptIn(DelicateCoroutinesApi::class)
|
||||
|
||||
package moe.fuqiuluo.shamrock.internals
|
||||
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
|
||||
import io.kritor.Scene
|
||||
import io.kritor.contact
|
||||
import io.kritor.event.MessageEvent
|
||||
import io.kritor.event.messageEvent
|
||||
import io.kritor.sender
|
||||
import io.kritor.event.*
|
||||
import io.kritor.common.PushMessageBody
|
||||
import io.kritor.common.Contact
|
||||
import io.kritor.common.Scene
|
||||
import io.kritor.common.Sender
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.flow.FlowCollector
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.io.core.BytePacketBuilder
|
||||
import qq.service.QQInterfaces
|
||||
import qq.service.contact.ContactHelper
|
||||
import qq.service.msg.toKritorEventMessages
|
||||
|
||||
internal object GlobalEventTransmitter: QQInterfaces() {
|
||||
private val messageEventFlow by lazy {
|
||||
MutableSharedFlow<Pair<MsgRecord, MessageEvent>>()
|
||||
internal object GlobalEventTransmitter : QQInterfaces() {
|
||||
private val MessageEventFlow by lazy {
|
||||
MutableSharedFlow<Pair<MsgRecord, PushMessageBody>>()
|
||||
}
|
||||
private val noticeEventFlow by lazy {
|
||||
MutableSharedFlow<NoticeEvent>()
|
||||
}
|
||||
private val requestEventFlow by lazy {
|
||||
MutableSharedFlow<RequestEvent>()
|
||||
}
|
||||
//private val noticeEventFlow by lazy {
|
||||
// MutableSharedFlow<NoticeEvent>()
|
||||
//}
|
||||
//private val requestEventFlow by lazy {
|
||||
// MutableSharedFlow<RequestEvent>()
|
||||
//}
|
||||
|
||||
//private suspend fun pushNotice(noticeEvent: NoticeEvent) = noticeEventFlow.emit(noticeEvent)
|
||||
private suspend fun pushNotice(noticeEvent: NoticeEvent) = noticeEventFlow.emit(noticeEvent)
|
||||
|
||||
//private suspend fun pushRequest(requestEvent: RequestEvent) = requestEventFlow.emit(requestEvent)
|
||||
private suspend fun pushRequest(requestEvent: RequestEvent) = requestEventFlow.emit(requestEvent)
|
||||
|
||||
private suspend fun transMessageEvent(record: MsgRecord, message: MessageEvent) = messageEventFlow.emit(record to message)
|
||||
private suspend fun transMessageEvent(record: MsgRecord, message: PushMessageBody) =
|
||||
MessageEventFlow.emit(record to message)
|
||||
|
||||
object MessageTransmitter {
|
||||
suspend fun transGroupMessage(
|
||||
record: MsgRecord,
|
||||
elements: ArrayList<MsgElement>,
|
||||
): Boolean {
|
||||
transMessageEvent(record, messageEvent {
|
||||
this.time = record.msgTime.toInt()
|
||||
this.scene = Scene.GROUP
|
||||
this.messageId = record.msgId
|
||||
transMessageEvent(record, PushMessageBody.newBuilder().apply {
|
||||
this.time = record.msgTime
|
||||
this.messageId = record.msgId.toString()
|
||||
this.messageSeq = record.msgSeq
|
||||
this.contact = contact {
|
||||
this.scene = scene
|
||||
this.contact = Contact.newBuilder().apply {
|
||||
this.scene = Scene.GROUP
|
||||
this.peer = record.peerUin.toString()
|
||||
this.subPeer = record.peerUid
|
||||
}
|
||||
this.sender = sender {
|
||||
}.build()
|
||||
this.sender = Sender.newBuilder().apply {
|
||||
this.uin = record.senderUin
|
||||
this.uid = record.senderUid
|
||||
this.nick = record.sendNickName
|
||||
}
|
||||
})
|
||||
}.build()
|
||||
this.addAllElements(elements.toKritorEventMessages(record))
|
||||
}.build())
|
||||
return true
|
||||
}
|
||||
|
||||
@ -59,9 +64,22 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
||||
record: MsgRecord,
|
||||
elements: ArrayList<MsgElement>,
|
||||
): Boolean {
|
||||
val botUin = app.longAccountUin
|
||||
var nickName = record.sendNickName
|
||||
|
||||
transMessageEvent(record, PushMessageBody.newBuilder().apply {
|
||||
this.time = record.msgTime
|
||||
this.messageId = record.msgId.toString()
|
||||
this.messageSeq = record.msgSeq
|
||||
this.contact = Contact.newBuilder().apply {
|
||||
this.scene = Scene.FRIEND
|
||||
this.peer = record.senderUid
|
||||
this.subPeer = record.senderUid
|
||||
}.build()
|
||||
this.sender = Sender.newBuilder().apply {
|
||||
this.uin = record.senderUin
|
||||
this.uid = record.senderUid
|
||||
this.nick = record.sendNickName
|
||||
}.build()
|
||||
this.addAllElements(elements.toKritorEventMessages(record))
|
||||
}.build())
|
||||
return true
|
||||
}
|
||||
|
||||
@ -71,9 +89,22 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
||||
groupCode: Long,
|
||||
fromNick: String,
|
||||
): Boolean {
|
||||
val botUin = app.longAccountUin
|
||||
var nickName = record.sendNickName
|
||||
|
||||
transMessageEvent(record, PushMessageBody.newBuilder().apply {
|
||||
this.time = record.msgTime
|
||||
this.messageId = record.msgId.toString()
|
||||
this.messageSeq = record.msgSeq
|
||||
this.contact = Contact.newBuilder().apply {
|
||||
this.scene = if (groupCode > 0) Scene.STRANGER_FROM_GROUP else Scene.STRANGER
|
||||
this.peer = record.senderUid
|
||||
this.subPeer = groupCode.toString()
|
||||
}.build()
|
||||
this.sender = Sender.newBuilder().apply {
|
||||
this.uin = record.senderUin
|
||||
this.uid = record.senderUid
|
||||
this.nick = record.sendNickName
|
||||
}.build()
|
||||
this.addAllElements(elements.toKritorEventMessages(record))
|
||||
}.build())
|
||||
return true
|
||||
}
|
||||
|
||||
@ -81,24 +112,37 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
||||
record: MsgRecord,
|
||||
elements: ArrayList<MsgElement>,
|
||||
): Boolean {
|
||||
val botUin = app.longAccountUin
|
||||
var nickName = record.sendNickName
|
||||
|
||||
transMessageEvent(record, PushMessageBody.newBuilder().apply {
|
||||
this.time = record.msgTime
|
||||
this.messageId = record.msgId.toString()
|
||||
this.messageSeq = record.msgSeq
|
||||
this.contact = Contact.newBuilder().apply {
|
||||
this.scene = Scene.GUILD
|
||||
this.peer = record.guildId ?: ""
|
||||
this.subPeer = record.channelId ?: ""
|
||||
}.build()
|
||||
this.sender = Sender.newBuilder().apply {
|
||||
this.uin = record.senderUin
|
||||
this.uid = record.senderUid
|
||||
this.nick = record.sendNickName
|
||||
}.build()
|
||||
this.addAllElements(elements.toKritorEventMessages(record))
|
||||
}.build())
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* 文件通知 通知器
|
||||
*/
|
||||
**/
|
||||
object FileNoticeTransmitter {
|
||||
/**
|
||||
* 推送私聊文件事件
|
||||
*/
|
||||
suspend fun transPrivateFileEvent(
|
||||
msgTime: Long,
|
||||
userId: Long,
|
||||
senderUid: String,
|
||||
senderUin: Long,
|
||||
fileId: String,
|
||||
fileSubId: String,
|
||||
fileName: String,
|
||||
@ -106,23 +150,20 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
||||
expireTime: Long,
|
||||
url: String
|
||||
): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = msgTime,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = NoticeType.PrivateUpload,
|
||||
operatorId = userId,
|
||||
userId = userId,
|
||||
senderId = userId,
|
||||
privateFile = PrivateFileMsg(
|
||||
id = fileId,
|
||||
name = fileName,
|
||||
size = fileSize,
|
||||
url = url,
|
||||
subId = fileSubId,
|
||||
expire = expireTime
|
||||
)
|
||||
))
|
||||
pushNotice(NoticeEvent.newBuilder().apply {
|
||||
this.type = NoticeEvent.NoticeType.PRIVATE_FILE_UPLOADED
|
||||
this.time = msgTime
|
||||
this.privateFileUploaded = PrivateFileUploadedNotice.newBuilder().apply {
|
||||
this.fileId = fileId
|
||||
this.fileName = fileName
|
||||
this.operatorUid = senderUid
|
||||
this.operatorUin = senderUin
|
||||
this.fileSize = fileSize
|
||||
this.expireTime = expireTime
|
||||
this.fileSubId = fileSubId.toInt() // todo(这玩意真的是一个数字?)
|
||||
this.fileUrl = url
|
||||
}.build()
|
||||
}.build())
|
||||
return true
|
||||
}
|
||||
|
||||
@ -131,7 +172,8 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
||||
*/
|
||||
suspend fun transGroupFileEvent(
|
||||
msgTime: Long,
|
||||
userId: Long,
|
||||
senderUid: String,
|
||||
senderUin: Long,
|
||||
groupId: Long,
|
||||
uuid: String,
|
||||
fileName: String,
|
||||
@ -139,22 +181,20 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
||||
bizId: Int,
|
||||
url: String
|
||||
): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = msgTime,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = NoticeType.GroupUpload,
|
||||
operatorId = userId,
|
||||
userId = userId,
|
||||
groupId = groupId,
|
||||
file = GroupFileMsg(
|
||||
id = uuid,
|
||||
name = fileName,
|
||||
size = fileSize,
|
||||
busid = bizId.toLong(),
|
||||
url = url
|
||||
)
|
||||
))
|
||||
pushNotice(NoticeEvent.newBuilder().apply {
|
||||
this.type = NoticeEvent.NoticeType.GROUP_FILE_UPLOADED
|
||||
this.time = msgTime
|
||||
this.groupFileUploaded = GroupFileUploadedNotice.newBuilder().apply {
|
||||
this.groupId = groupId
|
||||
this.operatorUid = senderUid
|
||||
this.operatorUin = senderUin
|
||||
this.fileId = uuid
|
||||
this.fileName = fileName
|
||||
this.fileSize = fileSize
|
||||
this.fileSubId = bizId
|
||||
this.fileUrl = url
|
||||
}.build()
|
||||
}.build())
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -163,69 +203,98 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
||||
* 群聊通知 通知器
|
||||
*/
|
||||
object GroupNoticeTransmitter {
|
||||
suspend fun transGroupSign(time: Long, target: Long, action: String?, rankImg: String?, groupCode: Long): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = time,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = NoticeType.Notify,
|
||||
subType = NoticeSubType.Sign,
|
||||
userId = target,
|
||||
groupId = groupCode,
|
||||
target = target,
|
||||
signDetail = SignDetail(
|
||||
rankImg = rankImg,
|
||||
action = action
|
||||
)
|
||||
))
|
||||
suspend fun transGroupSign(
|
||||
time: Long,
|
||||
target: Long,
|
||||
action: String,
|
||||
rankImg: String,
|
||||
groupCode: Long
|
||||
): Boolean {
|
||||
pushNotice(NoticeEvent.newBuilder().apply {
|
||||
this.type = NoticeEvent.NoticeType.GROUP_SIGN_IN
|
||||
this.time = time
|
||||
this.groupSignIn = GroupSignInNotice.newBuilder().apply {
|
||||
this.groupId = groupCode
|
||||
this.targetUid = ContactHelper.getUidByUinAsync(target)
|
||||
this.targetUin = target
|
||||
this.action = action
|
||||
this.rankImage = rankImg
|
||||
}.build()
|
||||
}.build())
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun transGroupPoke(time: Long, operation: Long, target: Long, action: String?, suffix: String?, actionImg: String?, groupCode: Long): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = time,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = NoticeType.Notify,
|
||||
subType = NoticeSubType.Poke,
|
||||
operatorId = operation,
|
||||
userId = operation,
|
||||
groupId = groupCode,
|
||||
target = target,
|
||||
pokeDetail = PokeDetail(
|
||||
action = action,
|
||||
suffix = suffix,
|
||||
actionImg = actionImg
|
||||
)
|
||||
))
|
||||
suspend fun transGroupPoke(
|
||||
time: Long,
|
||||
operator: Long,
|
||||
target: Long,
|
||||
action: String,
|
||||
suffix: String,
|
||||
actionImg: String,
|
||||
groupCode: Long
|
||||
): Boolean {
|
||||
pushNotice(NoticeEvent.newBuilder().apply {
|
||||
this.type = NoticeEvent.NoticeType.GROUP_POKE
|
||||
this.time = time
|
||||
this.groupPoke = GroupPokeNotice.newBuilder().apply {
|
||||
this.groupId = groupCode
|
||||
this.action = action
|
||||
this.targetUid = ContactHelper.getUidByUinAsync(target)
|
||||
this.targetUin = target
|
||||
this.operatorUid = ContactHelper.getUidByUinAsync(operator)
|
||||
this.operatorUin = operator
|
||||
this.suffix = suffix
|
||||
this.actionImage = actionImg
|
||||
}.build()
|
||||
}.build())
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun transGroupMemberNumChanged(
|
||||
suspend fun transGroupMemberNumIncreased(
|
||||
time: Long,
|
||||
target: Long,
|
||||
targetUid: String,
|
||||
groupCode: Long,
|
||||
operator: Long,
|
||||
operatorUid: String,
|
||||
noticeType: NoticeType,
|
||||
noticeSubType: NoticeSubType
|
||||
type: GroupMemberIncreasedNotice.GroupMemberIncreasedType
|
||||
): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = time,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = noticeType,
|
||||
subType = noticeSubType,
|
||||
operatorId = operator,
|
||||
userId = target,
|
||||
senderId = operator,
|
||||
target = target,
|
||||
groupId = groupCode,
|
||||
targetUid = targetUid,
|
||||
operatorUid = operatorUid,
|
||||
userUid = targetUid
|
||||
))
|
||||
pushNotice(NoticeEvent.newBuilder().apply {
|
||||
this.type = NoticeEvent.NoticeType.GROUP_MEMBER_INCREASE
|
||||
this.time = time
|
||||
this.groupMemberIncrease = GroupMemberIncreasedNotice.newBuilder().apply {
|
||||
this.groupId = groupCode
|
||||
this.operatorUid = operatorUid
|
||||
this.operatorUin = operator
|
||||
this.targetUid = targetUid
|
||||
this.targetUin = target
|
||||
this.type = type
|
||||
}.build()
|
||||
}.build())
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun transGroupMemberNumDecreased(
|
||||
time: Long,
|
||||
target: Long,
|
||||
targetUid: String,
|
||||
groupCode: Long,
|
||||
operator: Long,
|
||||
operatorUid: String,
|
||||
type: GroupMemberDecreasedNotice.GroupMemberDecreasedType
|
||||
): Boolean {
|
||||
pushNotice(NoticeEvent.newBuilder().apply {
|
||||
this.type = NoticeEvent.NoticeType.GROUP_MEMBER_DECREASE
|
||||
this.time = time
|
||||
this.groupMemberDecrease = GroupMemberDecreasedNotice.newBuilder().apply {
|
||||
this.groupId = groupCode
|
||||
this.operatorUid = operatorUid
|
||||
this.operatorUin = operator
|
||||
this.targetUid = targetUid
|
||||
this.targetUin = target
|
||||
this.type = type
|
||||
}.build()
|
||||
}.build())
|
||||
return true
|
||||
}
|
||||
|
||||
@ -236,25 +305,41 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
||||
groupCode: Long,
|
||||
setAdmin: Boolean
|
||||
): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = msgTime,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = NoticeType.GroupAdminChange,
|
||||
subType = if (setAdmin) NoticeSubType.Set else NoticeSubType.UnSet,
|
||||
operatorId = 0,
|
||||
userId = target,
|
||||
userUid = targetUid,
|
||||
target = target,
|
||||
targetUid = targetUid,
|
||||
groupId = groupCode
|
||||
))
|
||||
pushNotice(NoticeEvent.newBuilder().apply {
|
||||
this.type = NoticeEvent.NoticeType.GROUP_ADMIN_CHANGED
|
||||
this.time = msgTime
|
||||
this.groupAdminChanged = GroupAdminChangedNotice.newBuilder().apply {
|
||||
this.groupId = groupCode
|
||||
this.targetUid = targetUid
|
||||
this.targetUin = target
|
||||
this.isAdmin = setAdmin
|
||||
}.build()
|
||||
}.build())
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun transGroupWholeBan(
|
||||
msgTime: Long,
|
||||
groupCode: Long,
|
||||
operatorUid: String,
|
||||
operator: Long,
|
||||
isOpen: Boolean
|
||||
): Boolean {
|
||||
pushNotice(NoticeEvent.newBuilder().apply {
|
||||
this.type = NoticeEvent.NoticeType.GROUP_WHOLE_BAN
|
||||
this.time = msgTime
|
||||
this.groupWholeBan = GroupWholeBanNotice.newBuilder().apply {
|
||||
this.groupId = groupCode
|
||||
this.isBan = isOpen
|
||||
this.operatorUid = operatorUid
|
||||
this.operatorUin = operator
|
||||
}.build()
|
||||
}.build())
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun transGroupBan(
|
||||
msgTime: Long,
|
||||
subType: NoticeSubType,
|
||||
operator: Long,
|
||||
operatorUid: String,
|
||||
target: Long,
|
||||
@ -262,82 +347,83 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
||||
groupCode: Long,
|
||||
duration: Int
|
||||
): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = msgTime,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = NoticeType.GroupBan,
|
||||
subType = subType,
|
||||
operatorId = operator,
|
||||
userId = target,
|
||||
senderId = operator,
|
||||
target = target,
|
||||
groupId = groupCode,
|
||||
duration = duration,
|
||||
operatorUid = operatorUid,
|
||||
targetUid = targetUid
|
||||
))
|
||||
pushNotice(NoticeEvent.newBuilder().apply {
|
||||
this.type = NoticeEvent.NoticeType.GROUP_MEMBER_BAN
|
||||
this.time = msgTime
|
||||
this.groupMemberBan = GroupMemberBanNotice.newBuilder().apply {
|
||||
this.groupId = groupCode
|
||||
this.operatorUid = operatorUid
|
||||
this.operatorUin = operator
|
||||
this.targetUid = targetUid
|
||||
this.targetUin = target
|
||||
this.duration = duration
|
||||
this.type = if (duration > 0) GroupMemberBanNotice.GroupMemberBanType.BAN
|
||||
else GroupMemberBanNotice.GroupMemberBanType.LIFT_BAN
|
||||
}.build()
|
||||
}.build())
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun transGroupMsgRecall(
|
||||
time: Long,
|
||||
operator: Long,
|
||||
operatorUid: String,
|
||||
target: Long,
|
||||
targetUid: String,
|
||||
groupCode: Long,
|
||||
msgHash: Int,
|
||||
msgId: Long,
|
||||
tipText: String
|
||||
): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = time,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = NoticeType.GroupRecall,
|
||||
operatorId = operator,
|
||||
userId = target,
|
||||
msgId = msgHash,
|
||||
tip = tipText,
|
||||
groupId = groupCode
|
||||
))
|
||||
pushNotice(NoticeEvent.newBuilder().apply {
|
||||
this.type = NoticeEvent.NoticeType.GROUP_RECALL
|
||||
this.time = time
|
||||
this.groupRecall = GroupRecallNotice.newBuilder().apply {
|
||||
this.groupId = groupCode
|
||||
this.operatorUid = operatorUid
|
||||
this.operatorUin = operator
|
||||
this.targetUid = targetUid
|
||||
this.targetUin = target
|
||||
this.messageId = msgId.toString()
|
||||
this.tipText = tipText
|
||||
}.build()
|
||||
}.build())
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun transCardChange(
|
||||
time: Long,
|
||||
targetId: Long,
|
||||
oldCard: String,
|
||||
newCard: String,
|
||||
groupId: Long
|
||||
): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = time,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = NoticeType.GroupCard,
|
||||
userId = targetId,
|
||||
cardNew = newCard,
|
||||
cardOld = oldCard,
|
||||
groupId = groupId
|
||||
))
|
||||
pushNotice(NoticeEvent.newBuilder().apply {
|
||||
this.type = NoticeEvent.NoticeType.GROUP_CARD_CHANGED
|
||||
this.time = time
|
||||
this.groupCardChanged = GroupCardChangedNotice.newBuilder().apply {
|
||||
this.groupId = groupId
|
||||
this.targetUin = targetId
|
||||
this.newCard = newCard
|
||||
}.build()
|
||||
}.build())
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun transTitleChange(
|
||||
time: Long,
|
||||
targetId: Long,
|
||||
targetUin: Long,
|
||||
title: String,
|
||||
groupId: Long
|
||||
): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = time,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = NoticeType.Notify,
|
||||
userId = targetId,
|
||||
groupId = groupId,
|
||||
title = title,
|
||||
subType = NoticeSubType.Title
|
||||
))
|
||||
pushNotice(NoticeEvent.newBuilder().apply {
|
||||
this.type = NoticeEvent.NoticeType.GROUP_MEMBER_UNIQUE_TITLE_CHANGED
|
||||
this.time = time
|
||||
this.groupMemberUniqueTitleChanged = GroupUniqueTitleChangedNotice.newBuilder().apply {
|
||||
this.groupId = groupId
|
||||
this.targetUid = ContactHelper.getUidByUinAsync(targetUin)
|
||||
this.targetUin = targetUin
|
||||
this.title = title
|
||||
}.build()
|
||||
}.build())
|
||||
return true
|
||||
}
|
||||
|
||||
@ -345,21 +431,23 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
||||
time: Long,
|
||||
senderUin: Long,
|
||||
operatorUin: Long,
|
||||
msgId: Int,
|
||||
msgId: Long,
|
||||
groupId: Long,
|
||||
subType: NoticeSubType
|
||||
subType: UInt
|
||||
): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = time,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = NoticeType.Essence,
|
||||
senderId = senderUin,
|
||||
groupId = groupId,
|
||||
operatorId = operatorUin,
|
||||
msgId = msgId,
|
||||
subType = subType
|
||||
))
|
||||
pushNotice(NoticeEvent.newBuilder().apply {
|
||||
this.type = NoticeEvent.NoticeType.GROUP_ESSENCE_CHANGED
|
||||
this.time = time
|
||||
this.groupEssenceChanged = GroupEssenceMessageNotice.newBuilder().apply {
|
||||
this.groupId = groupId
|
||||
this.messageId = msgId.toString()
|
||||
this.targetUid = ContactHelper.getUidByUinAsync(targetUin)
|
||||
this.targetUin = senderUin
|
||||
this.operatorUid = ContactHelper.getUidByUinAsync(operatorUin)
|
||||
this.operatorUin = operatorUin
|
||||
this.isSet = subType.toInt() == 1
|
||||
}.build()
|
||||
}.build())
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -368,37 +456,37 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
||||
* 私聊通知 通知器
|
||||
*/
|
||||
object PrivateNoticeTransmitter {
|
||||
suspend fun transPrivatePoke(msgTime: Long, operation: Long, target: Long, action: String?, suffix: String?, actionImg: String?): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = msgTime,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = NoticeType.Notify,
|
||||
subType = NoticeSubType.Poke,
|
||||
operatorId = operation,
|
||||
userId = operation,
|
||||
senderId = operation,
|
||||
target = target,
|
||||
pokeDetail = PokeDetail(
|
||||
actionImg = actionImg,
|
||||
action = action,
|
||||
suffix = suffix
|
||||
)
|
||||
))
|
||||
suspend fun transPrivatePoke(
|
||||
msgTime: Long,
|
||||
operator: Long,
|
||||
action: String?,
|
||||
suffix: String?,
|
||||
actionImg: String?
|
||||
): Boolean {
|
||||
pushNotice(NoticeEvent.newBuilder().apply {
|
||||
this.type = NoticeEvent.NoticeType.PRIVATE_POKE
|
||||
this.time = msgTime
|
||||
this.privatePoke = PrivatePokeNotice.newBuilder().apply {
|
||||
this.action = action ?: ""
|
||||
this.operatorUid = ContactHelper.getUidByUinAsync(operator)
|
||||
this.operatorUin = operator
|
||||
this.suffix = suffix ?: ""
|
||||
this.actionImage = actionImg ?: ""
|
||||
}.build()
|
||||
}.build())
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun transPrivateRecall(time: Long, operation: Long, msgHashId: Int, tipText: String): Boolean {
|
||||
pushNotice(NoticeEvent(
|
||||
time = time,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Notice,
|
||||
type = NoticeType.FriendRecall,
|
||||
operatorId = operation,
|
||||
userId = operation,
|
||||
msgId = msgHashId,
|
||||
tip = tipText
|
||||
))
|
||||
suspend fun transPrivateRecall(time: Long, operator: Long, msgId: Long, tipText: String): Boolean {
|
||||
pushNotice(NoticeEvent.newBuilder().apply {
|
||||
this.type = NoticeEvent.NoticeType.PRIVATE_RECALL
|
||||
this.time = time
|
||||
this.privateRecall = PrivateRecallNotice.newBuilder().apply {
|
||||
this.operatorUin = operator
|
||||
this.messageId = msgId.toString()
|
||||
this.tipText = tipText
|
||||
}.build()
|
||||
}.build())
|
||||
return true
|
||||
}
|
||||
|
||||
@ -408,53 +496,71 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
||||
* 请求 通知器
|
||||
*/
|
||||
object RequestTransmitter {
|
||||
suspend fun transFriendApp(time: Long, operation: Long, tipText: String, flag: String): Boolean {
|
||||
pushRequest(RequestEvent(
|
||||
time = time,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Request,
|
||||
type = RequestType.Friend,
|
||||
userId = operation,
|
||||
comment = tipText,
|
||||
flag = flag
|
||||
))
|
||||
suspend fun transFriendApp(time: Long, applierUid: String, operator: Long, tipText: String, flag: String): Boolean {
|
||||
pushRequest(RequestEvent.newBuilder().apply {
|
||||
this.type = RequestEvent.RequestType.FRIEND_APPLY
|
||||
this.time = time
|
||||
this.requestId = flag
|
||||
this.friendApply = FriendApplyRequest.newBuilder().apply {
|
||||
this.applierUid = applierUid
|
||||
this.applierUin = operator
|
||||
this.message = tipText
|
||||
}.build()
|
||||
}.build())
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun transGroupApply(
|
||||
time: Long,
|
||||
applier: Long,
|
||||
applierUin: Long,
|
||||
applierUid: String,
|
||||
reason: String,
|
||||
groupCode: Long,
|
||||
flag: String,
|
||||
subType: RequestSubType
|
||||
flag: String
|
||||
): Boolean {
|
||||
pushRequest(RequestEvent(
|
||||
time = time,
|
||||
selfId = app.longAccountUin,
|
||||
postType = PostType.Request,
|
||||
type = RequestType.Group,
|
||||
userId = applier,
|
||||
userUid = applierUid,
|
||||
comment = reason,
|
||||
groupId = groupCode,
|
||||
subType = subType,
|
||||
flag = flag
|
||||
))
|
||||
pushRequest(RequestEvent.newBuilder().apply {
|
||||
this.type = RequestEvent.RequestType.GROUP_APPLY
|
||||
this.time = time
|
||||
this.requestId = flag
|
||||
this.groupApply = GroupApplyRequest.newBuilder().apply {
|
||||
this.applierUid = applierUid
|
||||
this.applierUin = applierUin
|
||||
this.groupId = groupCode
|
||||
this.reason = reason
|
||||
}.build()
|
||||
}.build())
|
||||
return true
|
||||
}
|
||||
}*/
|
||||
|
||||
suspend inline fun onMessageEvent(collector: FlowCollector<Pair<MsgRecord, MessageEvent>>) {
|
||||
messageEventFlow.collect {
|
||||
suspend fun transGroupInvite(
|
||||
time: Long,
|
||||
inviterUid: String,
|
||||
inviterUin: Long,
|
||||
groupCode: Long,
|
||||
flag: String
|
||||
): Boolean {
|
||||
pushRequest(RequestEvent.newBuilder().apply {
|
||||
this.type = RequestEvent.RequestType.GROUP_APPLY
|
||||
this.time = time
|
||||
this.requestId = flag
|
||||
this.invitedGroup = InvitedJoinGroupRequest.newBuilder().apply {
|
||||
this.inviterUid = inviterUid
|
||||
this.inviterUin = inviterUin
|
||||
this.groupId = groupCode
|
||||
}.build()
|
||||
}.build())
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
suspend inline fun onMessageEvent(collector: FlowCollector<Pair<MsgRecord, PushMessageBody>>) {
|
||||
MessageEventFlow.collect {
|
||||
GlobalScope.launch {
|
||||
collector.emit(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
suspend inline fun onNoticeEvent(collector: FlowCollector<NoticeEvent>) {
|
||||
noticeEventFlow.collect {
|
||||
GlobalScope.launch {
|
||||
@ -469,5 +575,5 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
||||
collector.emit(it)
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
33
xposed/src/main/java/moe/fuqiuluo/shamrock/tools/Trpc.kt
Normal file
33
xposed/src/main/java/moe/fuqiuluo/shamrock/tools/Trpc.kt
Normal file
@ -0,0 +1,33 @@
|
||||
package moe.fuqiuluo.shamrock.tools
|
||||
|
||||
import com.tencent.qphone.base.remote.FromServiceMsg
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
||||
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||
import protobuf.oidb.TrpcOidb
|
||||
import tencent.im.oidb.oidb_sso
|
||||
|
||||
fun FromServiceMsg.decodeToOidb(): oidb_sso.OIDBSSOPkg {
|
||||
return kotlin.runCatching {
|
||||
oidb_sso.OIDBSSOPkg().mergeFrom(wupBuffer.slice(4).let {
|
||||
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
|
||||
})
|
||||
}.getOrElse {
|
||||
oidb_sso.OIDBSSOPkg().mergeFrom(wupBuffer.let {
|
||||
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun FromServiceMsg.decodeToTrpcOidb(): TrpcOidb {
|
||||
return kotlin.runCatching {
|
||||
wupBuffer.slice(4).let {
|
||||
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
|
||||
}.decodeProtobuf<TrpcOidb>()
|
||||
}.getOrElse {
|
||||
wupBuffer.let {
|
||||
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
|
||||
}.decodeProtobuf<TrpcOidb>()
|
||||
}
|
||||
}
|
@ -6,17 +6,21 @@ import android.content.Context
|
||||
import android.content.Context.BATTERY_SERVICE
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.os.BatteryManager
|
||||
import android.os.Build
|
||||
import android.os.Process
|
||||
import android.provider.Settings
|
||||
import kotlinx.serialization.Serializable
|
||||
import moe.fuqiuluo.shamrock.tools.ShamrockVersion
|
||||
import mqq.app.MobileQQ
|
||||
import kotlin.random.Random
|
||||
|
||||
|
||||
internal object PlatformUtils {
|
||||
const val QQ_9_0_8_VER = 5540
|
||||
const val QQ_9_0_65_VER = 6566
|
||||
|
||||
fun getQUA(): String {
|
||||
return "V1_AND_SQ_${getQQVersion(MobileQQ.getContext())}_${getQQVersionCode()}_YYB_D"
|
||||
@ -69,6 +73,15 @@ internal object PlatformUtils {
|
||||
return MobileQQ.getMobileQQ().qqProcessName == "com.tencent.tim"
|
||||
}
|
||||
|
||||
fun isApkInDebug(context: Context): Boolean {
|
||||
try {
|
||||
val info = context.applicationInfo
|
||||
return (info.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
|
||||
} catch (e: Exception) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
fun killProcess(context: Context, processName: String) {
|
||||
for (processInfo in (context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).runningAppProcesses) {
|
||||
if (processInfo.processName == processName) {
|
||||
|
@ -7,6 +7,7 @@ import android.content.pm.PackageManager
|
||||
import android.content.pm.VersionedPackage
|
||||
import android.os.Build
|
||||
import android.os.Looper
|
||||
import com.tencent.qphone.base.remote.ToServiceMsg
|
||||
import de.robv.android.xposed.XC_MethodReplacement
|
||||
import de.robv.android.xposed.XSharedPreferences
|
||||
import de.robv.android.xposed.XposedHelpers
|
||||
@ -21,6 +22,8 @@ import moe.fuqiuluo.shamrock.xposed.XposedEntry
|
||||
import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader
|
||||
import moe.fuqiuluo.shamrock.xposed.loader.NativeLoader
|
||||
import moe.fuqiuluo.symbols.XposedHook
|
||||
import mqq.app.MobileQQ
|
||||
import qq.service.QQInterfaces
|
||||
|
||||
@XposedHook(priority = 0)
|
||||
class AntiDetection: IAction {
|
||||
@ -36,6 +39,17 @@ class AntiDetection: IAction {
|
||||
if (ShamrockConfig[AntiJvmTrace])
|
||||
antiTrace()
|
||||
antiMemoryWalking()
|
||||
//antiO3Report()
|
||||
}
|
||||
|
||||
private fun antiO3Report() {
|
||||
QQInterfaces.app.javaClass.hookMethod("sendToService").before {
|
||||
val toServiceMsg = it.args[0] as ToServiceMsg?
|
||||
if (toServiceMsg != null && toServiceMsg.serviceCmd.startsWith("trpc.o3")) {
|
||||
LogCenter.log("拦截trpc.o3环境上报包", Level.WARN)
|
||||
it.result = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun antiGetPackageGidsDetection(ctx: Context) {
|
||||
|
@ -6,8 +6,11 @@ import android.content.Context
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kritor.client.KritorClient
|
||||
import kritor.server.KritorServer
|
||||
import moe.fuqiuluo.shamrock.config.ActiveRPC
|
||||
import moe.fuqiuluo.shamrock.config.PassiveRPC
|
||||
import moe.fuqiuluo.shamrock.config.RPCAddress
|
||||
import moe.fuqiuluo.shamrock.config.RPCPort
|
||||
import moe.fuqiuluo.shamrock.config.ShamrockConfig
|
||||
import moe.fuqiuluo.shamrock.config.get
|
||||
@ -17,6 +20,7 @@ import moe.fuqiuluo.symbols.Process
|
||||
import moe.fuqiuluo.symbols.XposedHook
|
||||
|
||||
private lateinit var server: KritorServer
|
||||
private lateinit var client: KritorClient
|
||||
|
||||
@XposedHook(Process.MAIN, priority = 10)
|
||||
internal class InitRemoteService : IAction {
|
||||
@ -32,6 +36,21 @@ internal class InitRemoteService : IAction {
|
||||
LogCenter.log("ActiveRPC is disabled, KritorServer will not be started.")
|
||||
}
|
||||
|
||||
if (PassiveRPC.get()) {
|
||||
if (!::client.isInitialized) {
|
||||
val hostAndPort = RPCAddress.get().split(":").let {
|
||||
it.first() to it.last().toInt()
|
||||
}
|
||||
LogCenter.log("Connect RPC to ${hostAndPort.first}:${hostAndPort.second}")
|
||||
client = KritorClient(hostAndPort.first, hostAndPort.second)
|
||||
client.start()
|
||||
client.listen()
|
||||
|
||||
}
|
||||
} else {
|
||||
LogCenter.log("PassiveRPC is disabled, KritorServer will not be started.")
|
||||
}
|
||||
|
||||
|
||||
}.onFailure {
|
||||
LogCenter.log("Start RPC failed: ${it.message}", Level.ERROR)
|
||||
|
@ -9,11 +9,13 @@ import qq.service.QQInterfaces
|
||||
|
||||
object SwitchStatus: IInteract, QQInterfaces() {
|
||||
override fun invoke(intent: Intent) {
|
||||
AppTalker.talk("switch_status") {
|
||||
put("account", app.currentAccountUin)
|
||||
put("nickname", if (app is QQAppInterface) app.currentNickname else "unknown")
|
||||
put("voice", NativeLoader.isVoiceLoaded)
|
||||
put("core_version", ShamrockVersion)
|
||||
if (app.isLogin) {
|
||||
AppTalker.talk("switch_status") {
|
||||
put("account", app.currentAccountUin)
|
||||
put("nickname", if (app is QQAppInterface) (app.currentNickname ?: "unknown") else "unknown")
|
||||
put("voice", NativeLoader.isVoiceLoaded)
|
||||
put("core_version", ShamrockVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -83,6 +83,17 @@ abstract class QQInterfaces {
|
||||
app.sendToService(to)
|
||||
}
|
||||
|
||||
fun sendBuffer(
|
||||
cmd: String,
|
||||
isProto: Boolean,
|
||||
data: ByteArray,
|
||||
) {
|
||||
val toServiceMsg = createToServiceMsg(cmd)
|
||||
toServiceMsg.putWupBuffer(data)
|
||||
toServiceMsg.addAttribute("req_pb_protocol_flag", isProto)
|
||||
sendToServiceMsg(toServiceMsg)
|
||||
}
|
||||
|
||||
@DelicateCoroutinesApi
|
||||
suspend fun sendBufferAW(
|
||||
cmd: String,
|
||||
|
190
xposed/src/main/java/qq/service/bdh/FileTransfer.kt
Normal file
190
xposed/src/main/java/qq/service/bdh/FileTransfer.kt
Normal file
@ -0,0 +1,190 @@
|
||||
@file:OptIn(DelicateCoroutinesApi::class)
|
||||
|
||||
package qq.service.bdh
|
||||
|
||||
import com.tencent.mobileqq.transfile.BaseTransProcessor
|
||||
import com.tencent.mobileqq.transfile.FileMsg
|
||||
import com.tencent.mobileqq.transfile.TransferRequest
|
||||
import com.tencent.mobileqq.transfile.api.ITransFileController
|
||||
import com.tencent.mobileqq.utils.httputils.IHttpCommunicatorListener
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.utils.MD5
|
||||
import mqq.app.AppRuntime
|
||||
import qq.service.QQInterfaces
|
||||
import java.io.File
|
||||
import java.lang.Math.abs
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.random.Random
|
||||
|
||||
internal abstract class FileTransfer {
|
||||
suspend fun transC2CResource(
|
||||
peerId: String,
|
||||
file: File,
|
||||
fileType: Int, busiType: Int,
|
||||
wait: Boolean = true,
|
||||
builder: (TransferRequest) -> Unit
|
||||
): Boolean {
|
||||
val runtime = QQInterfaces.app
|
||||
val transferRequest = TransferRequest()
|
||||
transferRequest.needSendMsg = false
|
||||
transferRequest.mSelfUin = runtime.account
|
||||
transferRequest.mPeerUin = peerId
|
||||
transferRequest.mSecondId = runtime.currentAccountUin
|
||||
transferRequest.mUinType = FileMsg.UIN_BUDDY
|
||||
transferRequest.mFileType = fileType
|
||||
transferRequest.mUniseq = createMessageUniseq()
|
||||
transferRequest.mIsUp = true
|
||||
builder(transferRequest)
|
||||
transferRequest.mBusiType = busiType
|
||||
transferRequest.mMd5 = MD5.genFileMd5Hex(file.absolutePath)
|
||||
transferRequest.mLocalPath = file.absolutePath
|
||||
return transAndWait(runtime, transferRequest, wait)
|
||||
}
|
||||
|
||||
suspend fun transTroopResource(
|
||||
groupId: String,
|
||||
file: File,
|
||||
fileType: Int, busiType: Int,
|
||||
wait: Boolean = true,
|
||||
builder: (TransferRequest) -> Unit
|
||||
): Boolean {
|
||||
val runtime = QQInterfaces.app
|
||||
val transferRequest = TransferRequest()
|
||||
transferRequest.needSendMsg = false
|
||||
transferRequest.mSelfUin = runtime.account
|
||||
transferRequest.mPeerUin = groupId
|
||||
transferRequest.mSecondId = runtime.currentAccountUin
|
||||
transferRequest.mUinType = FileMsg.UIN_TROOP
|
||||
transferRequest.mFileType = fileType
|
||||
transferRequest.mUniseq = createMessageUniseq()
|
||||
transferRequest.mIsUp = true
|
||||
builder(transferRequest)
|
||||
transferRequest.mBusiType = busiType
|
||||
transferRequest.mMd5 = MD5.genFileMd5Hex(file.absolutePath)
|
||||
transferRequest.mLocalPath = file.absolutePath
|
||||
return transAndWait(runtime, transferRequest, wait)
|
||||
}
|
||||
|
||||
private suspend fun transAndWait(
|
||||
runtime: AppRuntime,
|
||||
transferRequest: TransferRequest,
|
||||
wait: Boolean
|
||||
): Boolean {
|
||||
return withTimeoutOrNull(60_000) {
|
||||
val service = runtime.getRuntimeService(ITransFileController::class.java, "all")
|
||||
if(service.transferAsync(transferRequest)) {
|
||||
if (!wait) { // 如果无需等待直接返回
|
||||
return@withTimeoutOrNull true
|
||||
}
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
GlobalScope.launch {
|
||||
lateinit var processor: IHttpCommunicatorListener
|
||||
while (
|
||||
//service.findProcessor(
|
||||
// transferRequest.keyForTransfer // uin + uniseq
|
||||
//) != null
|
||||
service.containsProcessor(runtime.currentAccountUin, transferRequest.mUniseq)
|
||||
// 如果上传处理器依旧存在,说明没有上传成功
|
||||
&& service.isWorking.get()
|
||||
) {
|
||||
processor = service.findProcessor(runtime.currentAccountUin, transferRequest.mUniseq)
|
||||
delay(100)
|
||||
}
|
||||
if (processor is BaseTransProcessor && processor.file != null) {
|
||||
val fileMsg = processor.file
|
||||
LogCenter.log("[OldBDH] 资源上传结束(fileId = ${fileMsg.fileID}, fileKey = ${fileMsg.fileKey}, path = ${fileMsg.filePath})")
|
||||
}
|
||||
continuation.resume(true)
|
||||
}
|
||||
// 实现取消上传器
|
||||
// 目前没什么用
|
||||
continuation.invokeOnCancellation {
|
||||
continuation.resume(false)
|
||||
}
|
||||
}
|
||||
} else true
|
||||
} ?: false
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SEND_MSG_BUSINESS_TYPE_AIO_ALBUM_PIC = 1031
|
||||
const val SEND_MSG_BUSINESS_TYPE_AIO_KEY_WORD_PIC = 1046
|
||||
const val SEND_MSG_BUSINESS_TYPE_AIO_QZONE_PIC = 1045
|
||||
const val SEND_MSG_BUSINESS_TYPE_ALBUM_PIC = 1007
|
||||
const val SEND_MSG_BUSINESS_TYPE_BLESS = 1056
|
||||
const val SEND_MSG_BUSINESS_TYPE_CAPTURE_PIC = 1008
|
||||
const val SEND_MSG_BUSINESS_TYPE_COMMEN_FALSH_PIC = 1040
|
||||
const val SEND_MSG_BUSINESS_TYPE_CUSTOM = 1006
|
||||
const val SEND_MSG_BUSINESS_TYPE_DOUTU_PIC = 1044
|
||||
const val SEND_MSG_BUSINESS_TYPE_FALSH_PIC = 1039
|
||||
const val SEND_MSG_BUSINESS_TYPE_FAST_IMAGE = 1037
|
||||
const val SEND_MSG_BUSINESS_TYPE_FORWARD_EDIT = 1048
|
||||
const val SEND_MSG_BUSINESS_TYPE_FORWARD_PIC = 1009
|
||||
const val SEND_MSG_BUSINESS_TYPE_FULL_SCREEN_ESSENCE = 1057
|
||||
const val SEND_MSG_BUSINESS_TYPE_GALEERY_PIC = 1041
|
||||
const val SEND_MSG_BUSINESS_TYPE_GAME_CENTER_STRATEGY = 1058
|
||||
const val SEND_MSG_BUSINESS_TYPE_HOT_PIC = 1042
|
||||
const val SEND_MSG_BUSINESS_TYPE_MIXED_PICS = 1043
|
||||
const val SEND_MSG_BUSINESS_TYPE_PIC_AIO_ALBUM = 1052
|
||||
const val SEND_MSG_BUSINESS_TYPE_PIC_CAMERA = 1050
|
||||
const val SEND_MSG_BUSINESS_TYPE_PIC_FAV = 1053
|
||||
const val SEND_MSG_BUSINESS_TYPE_PIC_SCREEN = 1027
|
||||
const val SEND_MSG_BUSINESS_TYPE_PIC_SHARE = 1030
|
||||
const val SEND_MSG_BUSINESS_TYPE_PIC_TAB_CAMERA = 1051
|
||||
const val SEND_MSG_BUSINESS_TYPE_QQPINYIN_SEND_PIC = 1038
|
||||
const val SEND_MSG_BUSINESS_TYPE_RECOMMENDED_STICKER = 1047
|
||||
const val SEND_MSG_BUSINESS_TYPE_RELATED_EMOTION = 1054
|
||||
const val SEND_MSG_BUSINESS_TYPE_SHOWLOVE = 1036
|
||||
const val SEND_MSG_BUSINESS_TYPE_SOGOU_SEND_PIC = 1034
|
||||
const val SEND_MSG_BUSINESS_TYPE_TROOP_BAR = 1035
|
||||
const val SEND_MSG_BUSINESS_TYPE_WLAN_RECV_NOTIFY = 1055
|
||||
const val SEND_MSG_BUSINESS_TYPE_ZHITU_PIC = 1049
|
||||
const val SEND_MSG_BUSINESS_TYPE_ZPLAN_EMOTICON_GIF = 1060
|
||||
const val SEND_MSG_BUSINESS_TYPE_ZPLAN_PIC = 1059
|
||||
|
||||
const val VIDEO_FORMAT_AFS = 7
|
||||
const val VIDEO_FORMAT_AVI = 1
|
||||
const val VIDEO_FORMAT_MKV = 4
|
||||
const val VIDEO_FORMAT_MOD = 9
|
||||
const val VIDEO_FORMAT_MOV = 8
|
||||
const val VIDEO_FORMAT_MP4 = 2
|
||||
const val VIDEO_FORMAT_MTS = 11
|
||||
const val VIDEO_FORMAT_RM = 6
|
||||
const val VIDEO_FORMAT_RMVB = 5
|
||||
const val VIDEO_FORMAT_TS = 10
|
||||
const val VIDEO_FORMAT_WMV = 3
|
||||
|
||||
const val BUSI_TYPE_GUILD_VIDEO = 4601
|
||||
const val BUSI_TYPE_MULTI_FORWARD_VIDEO = 1010
|
||||
const val BUSI_TYPE_PUBACCOUNT_PERM_VIDEO = 1009
|
||||
const val BUSI_TYPE_PUBACCOUNT_TEMP_VIDEO = 1007
|
||||
const val BUSI_TYPE_SHORT_VIDEO = 1
|
||||
const val BUSI_TYPE_SHORT_VIDEO_PTV = 2
|
||||
const val BUSI_TYPE_VIDEO = 0
|
||||
const val BUSI_TYPE_VIDEO_EMOTICON_PIC = 1022
|
||||
const val BUSI_TYPE_VIDEO_EMOTICON_VIDEO = 1021
|
||||
|
||||
const val TRANSFILE_TYPE_PIC = 1
|
||||
const val TRANSFILE_TYPE_PIC_EMO = 65538
|
||||
const val TRANSFILE_TYPE_PIC_THUMB = 65537
|
||||
const val TRANSFILE_TYPE_PISMA = 49
|
||||
const val TRANSFILE_TYPE_RAWPIC = 131075
|
||||
|
||||
const val TRANSFILE_TYPE_PROFILE_COVER = 35
|
||||
const val TRANSFILE_TYPE_PTT = 2
|
||||
const val TRANSFILE_TYPE_PTT_SLICE_TO_TEXT = 327696
|
||||
const val TRANSFILE_TYPE_QQHEAD_PIC = 131074
|
||||
|
||||
internal fun createMessageUniseq(time: Long = System.currentTimeMillis()): Long {
|
||||
var uniseq = (time / 1000).toInt().toLong()
|
||||
uniseq = uniseq shl 32 or kotlin.math.abs(Random.nextInt()).toLong()
|
||||
return uniseq
|
||||
}
|
||||
}
|
||||
}
|
556
xposed/src/main/java/qq/service/bdh/NtV2RichMediaSvc.kt
Normal file
556
xposed/src/main/java/qq/service/bdh/NtV2RichMediaSvc.kt
Normal file
@ -0,0 +1,556 @@
|
||||
package qq.service.bdh
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import androidx.exifinterface.media.ExifInterface
|
||||
import com.tencent.mobileqq.qroute.QRoute
|
||||
import com.tencent.qqnt.aio.adapter.api.IAIOPttApi
|
||||
import com.tencent.qqnt.kernel.nativeinterface.CommonFileInfo
|
||||
import com.tencent.qqnt.kernelpublic.nativeinterface.Contact
|
||||
import com.tencent.qqnt.kernel.nativeinterface.FileTransNotifyInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
||||
import com.tencent.qqnt.kernel.nativeinterface.PicElement
|
||||
import com.tencent.qqnt.kernel.nativeinterface.PttElement
|
||||
import com.tencent.qqnt.kernel.nativeinterface.QQNTWrapperUtil
|
||||
import com.tencent.qqnt.kernel.nativeinterface.RichMediaFilePathInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.VideoElement
|
||||
import com.tencent.qqnt.msg.api.IMsgService
|
||||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import moe.fuqiuluo.shamrock.config.ResourceGroup
|
||||
import moe.fuqiuluo.shamrock.config.ShamrockConfig
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToTrpcOidb
|
||||
import moe.fuqiuluo.shamrock.tools.hex2ByteArray
|
||||
import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import moe.fuqiuluo.shamrock.tools.toHexString
|
||||
import moe.fuqiuluo.shamrock.utils.AudioUtils
|
||||
import moe.fuqiuluo.shamrock.utils.FileUtils
|
||||
import moe.fuqiuluo.shamrock.utils.MediaType
|
||||
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||
import protobuf.auto.toByteArray
|
||||
import protobuf.oidb.TrpcOidb
|
||||
import protobuf.oidb.cmd0x11c5.ClientMeta
|
||||
import protobuf.oidb.cmd0x11c5.CodecConfigReq
|
||||
import protobuf.oidb.cmd0x11c5.CommonHead
|
||||
import protobuf.oidb.cmd0x11c5.DownloadExt
|
||||
import protobuf.oidb.cmd0x11c5.DownloadReq
|
||||
import protobuf.oidb.cmd0x11c5.DownloadRkeyReq
|
||||
import protobuf.oidb.cmd0x11c5.DownloadRkeyRsp
|
||||
import protobuf.oidb.cmd0x11c5.FileInfo
|
||||
import protobuf.oidb.cmd0x11c5.FileType
|
||||
import protobuf.oidb.cmd0x11c5.IndexNode
|
||||
import protobuf.oidb.cmd0x11c5.MultiMediaReqHead
|
||||
import protobuf.oidb.cmd0x11c5.NtV2RichMediaReq
|
||||
import protobuf.oidb.cmd0x11c5.NtV2RichMediaRsp
|
||||
import protobuf.oidb.cmd0x11c5.SceneInfo
|
||||
import protobuf.oidb.cmd0x11c5.UploadInfo
|
||||
import protobuf.oidb.cmd0x11c5.UploadReq
|
||||
import protobuf.oidb.cmd0x11c5.UploadRsp
|
||||
import protobuf.oidb.cmd0x11c5.VideoDownloadExt
|
||||
import protobuf.oidb.cmd0x388.Cmd0x388ReqBody
|
||||
import protobuf.oidb.cmd0x388.Cmd0x388RspBody
|
||||
import protobuf.oidb.cmd0x388.TryUpImgReq
|
||||
import qq.service.QQInterfaces
|
||||
import qq.service.internals.NTServiceFetcher
|
||||
import qq.service.internals.msgService
|
||||
import qq.service.kernel.SimpleKernelMsgListener
|
||||
import qq.service.msg.MessageHelper
|
||||
import java.io.File
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.random.Random
|
||||
import kotlin.random.nextUInt
|
||||
import kotlin.random.nextULong
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
internal object NtV2RichMediaSvc: QQInterfaces() {
|
||||
private val requestIdSeq = atomic(1L)
|
||||
|
||||
fun fetchGroupResUploadTo(): String {
|
||||
return ShamrockConfig[ResourceGroup].ifNullOrEmpty { "100000000" }!!
|
||||
}
|
||||
|
||||
suspend fun tryUploadResourceByNt(
|
||||
chatType: Int,
|
||||
elementType: Int,
|
||||
resources: ArrayList<File>,
|
||||
timeout: Duration,
|
||||
retryCnt: Int = 5
|
||||
): Result<MutableList<CommonFileInfo>> {
|
||||
return internalTryUploadResourceByNt(chatType, elementType, resources, timeout).onFailure {
|
||||
if (retryCnt > 0) {
|
||||
return tryUploadResourceByNt(chatType, elementType, resources, timeout, retryCnt - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量上传图片
|
||||
*/
|
||||
private suspend fun internalTryUploadResourceByNt(
|
||||
chatType: Int,
|
||||
elementType: Int,
|
||||
resources: ArrayList<File>,
|
||||
timeout: Duration
|
||||
): Result<MutableList<CommonFileInfo>> {
|
||||
require(resources.size in 1 .. 10) { "imageFiles.size() must be in 1 .. 10" }
|
||||
|
||||
val messages = ArrayList(resources.map { file ->
|
||||
val elem = MsgElement()
|
||||
elem.elementType = elementType
|
||||
when(elementType) {
|
||||
MsgConstant.KELEMTYPEPIC -> {
|
||||
val pic = PicElement()
|
||||
pic.md5HexStr = QQNTWrapperUtil.CppProxy.genFileMd5Hex(file.absolutePath)
|
||||
val msgService = NTServiceFetcher.kernelService.msgService!!
|
||||
val originalPath = msgService.getRichMediaFilePathForMobileQQSend(
|
||||
RichMediaFilePathInfo(
|
||||
2, 0, pic.md5HexStr, file.name, 1, 0, null, "", true
|
||||
)
|
||||
)
|
||||
if (!QQNTWrapperUtil.CppProxy.fileIsExist(originalPath) || QQNTWrapperUtil.CppProxy.getFileSize(
|
||||
originalPath
|
||||
) != file.length()
|
||||
) {
|
||||
val thumbPath = msgService.getRichMediaFilePathForMobileQQSend(
|
||||
RichMediaFilePathInfo(
|
||||
2, 0, pic.md5HexStr, file.name, 2, 720, null, "", true
|
||||
)
|
||||
)
|
||||
QQNTWrapperUtil.CppProxy.copyFile(file.absolutePath, originalPath)
|
||||
QQNTWrapperUtil.CppProxy.copyFile(file.absolutePath, thumbPath)
|
||||
}
|
||||
val options = BitmapFactory.Options()
|
||||
options.inJustDecodeBounds = true
|
||||
BitmapFactory.decodeFile(file.absolutePath, options)
|
||||
val exifInterface = ExifInterface(file.absolutePath)
|
||||
val orientation = exifInterface.getAttributeInt(
|
||||
ExifInterface.TAG_ORIENTATION,
|
||||
ExifInterface.ORIENTATION_UNDEFINED
|
||||
)
|
||||
if (orientation != ExifInterface.ORIENTATION_ROTATE_90 && orientation != ExifInterface.ORIENTATION_ROTATE_270) {
|
||||
pic.picWidth = options.outWidth
|
||||
pic.picHeight = options.outHeight
|
||||
} else {
|
||||
pic.picWidth = options.outHeight
|
||||
pic.picHeight = options.outWidth
|
||||
}
|
||||
pic.sourcePath = file.absolutePath
|
||||
pic.fileSize = QQNTWrapperUtil.CppProxy.getFileSize(file.absolutePath)
|
||||
pic.original = true
|
||||
pic.picType = FileUtils.getPicType(file)
|
||||
elem.picElement = pic
|
||||
}
|
||||
MsgConstant.KELEMTYPEPTT -> {
|
||||
require(resources.size == 1) // 语音只能单个上传
|
||||
var pttFile = file
|
||||
val ptt = PttElement()
|
||||
when (AudioUtils.getMediaType(pttFile)) {
|
||||
MediaType.Silk -> {
|
||||
ptt.formatType = MsgConstant.KPTTFORMATTYPESILK
|
||||
ptt.duration = QRoute.api(IAIOPttApi::class.java)
|
||||
.getPttFileDuration(pttFile.absolutePath)
|
||||
}
|
||||
MediaType.Amr -> {
|
||||
ptt.duration = AudioUtils.getDurationSec(pttFile)
|
||||
ptt.formatType = MsgConstant.KPTTFORMATTYPEAMR
|
||||
}
|
||||
MediaType.Pcm -> {
|
||||
val result = AudioUtils.pcmToSilk(pttFile)
|
||||
ptt.duration = (result.second * 0.001).roundToInt()
|
||||
pttFile = result.first
|
||||
ptt.formatType = MsgConstant.KPTTFORMATTYPESILK
|
||||
}
|
||||
|
||||
else -> {
|
||||
val result = AudioUtils.audioToSilk(pttFile)
|
||||
ptt.duration = runCatching {
|
||||
QRoute.api(IAIOPttApi::class.java)
|
||||
.getPttFileDuration(result.second.absolutePath)
|
||||
}.getOrElse {
|
||||
result.first
|
||||
}
|
||||
pttFile = result.second
|
||||
ptt.formatType = MsgConstant.KPTTFORMATTYPESILK
|
||||
}
|
||||
}
|
||||
ptt.md5HexStr = QQNTWrapperUtil.CppProxy.genFileMd5Hex(pttFile.absolutePath)
|
||||
val msgService = NTServiceFetcher.kernelService.msgService!!
|
||||
val originalPath = msgService.getRichMediaFilePathForMobileQQSend(
|
||||
RichMediaFilePathInfo(
|
||||
MsgConstant.KELEMTYPEPTT, 0, ptt.md5HexStr, file.name, 1, 0, null, "", true
|
||||
)
|
||||
)
|
||||
if (!QQNTWrapperUtil.CppProxy.fileIsExist(originalPath) || QQNTWrapperUtil.CppProxy.getFileSize(originalPath) != pttFile.length()) {
|
||||
QQNTWrapperUtil.CppProxy.copyFile(pttFile.absolutePath, originalPath)
|
||||
}
|
||||
if (originalPath != null) {
|
||||
ptt.filePath = originalPath
|
||||
} else {
|
||||
ptt.filePath = pttFile.absolutePath
|
||||
}
|
||||
ptt.canConvert2Text = true
|
||||
ptt.fileId = 0
|
||||
ptt.fileUuid = ""
|
||||
ptt.text = ""
|
||||
ptt.voiceType = MsgConstant.KPTTVOICETYPESOUNDRECORD
|
||||
ptt.voiceChangeType = MsgConstant.KPTTVOICECHANGETYPENONE
|
||||
elem.pttElement = ptt
|
||||
}
|
||||
MsgConstant.KELEMTYPEVIDEO -> {
|
||||
require(resources.size == 1) // 视频只能单个上传
|
||||
val video = VideoElement()
|
||||
video.videoMd5 = QQNTWrapperUtil.CppProxy.genFileMd5Hex(file.absolutePath)
|
||||
val msgService = NTServiceFetcher.kernelService.msgService!!
|
||||
val originalPath = msgService.getRichMediaFilePathForMobileQQSend(
|
||||
RichMediaFilePathInfo(
|
||||
5, 2, video.videoMd5, file.name, 1, 0, null, "", true
|
||||
)
|
||||
)
|
||||
val thumbPath = msgService.getRichMediaFilePathForMobileQQSend(
|
||||
RichMediaFilePathInfo(
|
||||
5, 1, video.videoMd5, file.name, 2, 0, null, "", true
|
||||
)
|
||||
)
|
||||
if (!QQNTWrapperUtil.CppProxy.fileIsExist(originalPath) || QQNTWrapperUtil.CppProxy.getFileSize(
|
||||
originalPath
|
||||
) != file.length()
|
||||
) {
|
||||
QQNTWrapperUtil.CppProxy.copyFile(file.absolutePath, originalPath)
|
||||
AudioUtils.obtainVideoCover(file.absolutePath, thumbPath!!)
|
||||
}
|
||||
video.fileTime = AudioUtils.getVideoTime(file)
|
||||
video.fileSize = file.length()
|
||||
video.fileName = file.name
|
||||
video.fileFormat = FileTransfer.VIDEO_FORMAT_MP4
|
||||
video.thumbSize = QQNTWrapperUtil.CppProxy.getFileSize(thumbPath).toInt()
|
||||
val options = BitmapFactory.Options()
|
||||
BitmapFactory.decodeFile(thumbPath, options)
|
||||
video.thumbWidth = options.outWidth
|
||||
video.thumbHeight = options.outHeight
|
||||
video.thumbMd5 = QQNTWrapperUtil.CppProxy.genFileMd5Hex(thumbPath)
|
||||
video.thumbPath = hashMapOf(0 to thumbPath)
|
||||
elem.videoElement = video
|
||||
}
|
||||
else -> throw IllegalArgumentException("unsupported elementType: $elementType")
|
||||
}
|
||||
return@map elem
|
||||
})
|
||||
if (messages.isEmpty()) {
|
||||
return Result.failure(Exception("no valid image files"))
|
||||
}
|
||||
val contact = when(chatType) {
|
||||
MsgConstant.KCHATTYPEC2C -> MessageHelper.generateContact(chatType, app.currentAccountUin)
|
||||
else -> Contact(chatType, fetchGroupResUploadTo(), null)
|
||||
}
|
||||
val result = mutableListOf<CommonFileInfo>()
|
||||
val msgService = NTServiceFetcher.kernelService.msgService
|
||||
?: return Result.failure(Exception("kernelService.msgService is null"))
|
||||
withTimeoutOrNull(timeout) {
|
||||
val uniseq = MessageHelper.generateMsgId(chatType)
|
||||
suspendCancellableCoroutine {
|
||||
val listener = object: SimpleKernelMsgListener() {
|
||||
override fun onRichMediaUploadComplete(fileTransNotifyInfo: FileTransNotifyInfo) {
|
||||
if (fileTransNotifyInfo.msgId == uniseq) {
|
||||
result.add(fileTransNotifyInfo.commonFileInfo)
|
||||
}
|
||||
if (result.size == resources.size) {
|
||||
msgService.removeMsgListener(this)
|
||||
it.resume(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
msgService.addMsgListener(listener)
|
||||
|
||||
QRoute.api(IMsgService::class.java).sendMsg(contact, uniseq, messages) { _, _ ->
|
||||
if (contact.chatType == MsgConstant.KCHATTYPEGROUP && contact.peerUid == "100000000") {
|
||||
val kernelService = NTServiceFetcher.kernelService
|
||||
val sessionService = kernelService.wrapperSession
|
||||
val service = sessionService.msgService
|
||||
service.deleteMsg(contact, arrayListOf(uniseq), null)
|
||||
}
|
||||
}
|
||||
|
||||
it.invokeOnCancellation {
|
||||
msgService.removeMsgListener(listener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result.isEmpty()) {
|
||||
return Result.failure(Exception("upload failed"))
|
||||
}
|
||||
|
||||
return Result.success(result)
|
||||
}
|
||||
|
||||
suspend fun getTempNtRKey(): Result<DownloadRkeyRsp> {
|
||||
runCatching {
|
||||
val req = NtV2RichMediaReq(
|
||||
head = MultiMediaReqHead(
|
||||
commonHead = CommonHead(
|
||||
requestId = requestIdSeq.incrementAndGet().toULong(),
|
||||
cmd = 202u
|
||||
),
|
||||
sceneInfo = SceneInfo(
|
||||
requestType = 2u,
|
||||
businessType = 1u,
|
||||
sceneType = 0u,
|
||||
),
|
||||
clientMeta = ClientMeta(2u)
|
||||
),
|
||||
downloadRkey = DownloadRkeyReq(
|
||||
types = listOf(10, 20),
|
||||
downloadType = 2
|
||||
)
|
||||
).toByteArray()
|
||||
val fromServiceMsg = sendOidbAW("OidbSvcTrpcTcp.0x9067_202", 0x9067, 202, req, true)
|
||||
if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) {
|
||||
return Result.failure(Exception("failed to fetch NtTempRKey: ${fromServiceMsg?.wupBuffer?.toHexString()}"))
|
||||
}
|
||||
val trpc = fromServiceMsg.decodeToTrpcOidb()
|
||||
if (trpc.buffer == null) {
|
||||
return Result.failure(Exception("failed to fetch NtTempRKey: ${trpc.msg}"))
|
||||
}
|
||||
|
||||
trpc.buffer?.decodeProtobuf<NtV2RichMediaRsp>()?.downloadRkeyRsp?.let {
|
||||
return Result.success(it)
|
||||
}
|
||||
}.onFailure {
|
||||
return Result.failure(it)
|
||||
}
|
||||
return Result.failure(Exception("failed to fetch NtTempRKey"))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取NT图片的RKEY
|
||||
*/
|
||||
suspend fun getNtPicRKey(
|
||||
fileId: String,
|
||||
md5: String,
|
||||
sha: String,
|
||||
fileSize: ULong,
|
||||
width: UInt,
|
||||
height: UInt,
|
||||
sceneBuilder: suspend SceneInfo.() -> Unit
|
||||
): Result<String> {
|
||||
runCatching {
|
||||
val req = NtV2RichMediaReq(
|
||||
head = MultiMediaReqHead(
|
||||
commonHead = CommonHead(
|
||||
requestId = requestIdSeq.incrementAndGet().toULong(),
|
||||
cmd = 200u
|
||||
),
|
||||
sceneInfo = SceneInfo(
|
||||
requestType = 2u,
|
||||
businessType = 1u,
|
||||
).apply {
|
||||
sceneBuilder()
|
||||
},
|
||||
clientMeta = ClientMeta(2u)
|
||||
),
|
||||
download = DownloadReq(
|
||||
IndexNode(
|
||||
FileInfo(
|
||||
fileSize = fileSize,
|
||||
md5 = md5.lowercase(),
|
||||
sha1 = sha.lowercase(),
|
||||
name = "${md5}.jpg",
|
||||
fileType = FileType(
|
||||
fileType = 1u,
|
||||
picFormat = 1000u,
|
||||
videoFormat = 0u,
|
||||
voiceFormat = 0u
|
||||
),
|
||||
width = width,
|
||||
height = height,
|
||||
time = 0u,
|
||||
original = 1u
|
||||
),
|
||||
fileUuid = fileId,
|
||||
storeId = 1u,
|
||||
uploadTime = 0u,
|
||||
ttl = 0u,
|
||||
subType = 0u,
|
||||
storeAppId = 0u
|
||||
),
|
||||
DownloadExt(
|
||||
video = VideoDownloadExt(
|
||||
busiType = 0u,
|
||||
subBusiType = 0u,
|
||||
msgCodecConfig = CodecConfigReq(
|
||||
platformChipinfo = "",
|
||||
osVer = "",
|
||||
deviceName = ""
|
||||
),
|
||||
flag = 1u
|
||||
)
|
||||
)
|
||||
)
|
||||
).toByteArray()
|
||||
val fromServiceMsg = sendOidbAW("OidbSvcTrpcTcp.0x11c5_200", 4549, 200, req, true)
|
||||
if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) {
|
||||
return Result.failure(Exception("unable to get multimedia pic info: ${fromServiceMsg?.wupBuffer?.toHexString()}"))
|
||||
}
|
||||
val trpc = fromServiceMsg.decodeToTrpcOidb()
|
||||
if (trpc.buffer == null) {
|
||||
return Result.failure(Exception("unable to get multimedia pic info: ${trpc.msg}"))
|
||||
}
|
||||
|
||||
trpc.buffer?.decodeProtobuf<NtV2RichMediaRsp>()?.download?.rkeyParam?.let {
|
||||
return Result.success(it)
|
||||
}
|
||||
}.onFailure {
|
||||
return Result.failure(it)
|
||||
}
|
||||
return Result.failure(Exception("unable to get c2c nt pic"))
|
||||
}
|
||||
|
||||
suspend fun requestUploadNtPic(
|
||||
file: File,
|
||||
md5: String,
|
||||
sha: String,
|
||||
name: String,
|
||||
width: UInt,
|
||||
height: UInt,
|
||||
retryCnt: Int,
|
||||
chatType: Int,
|
||||
sceneBuilder: suspend SceneInfo.() -> Unit
|
||||
): Result<UploadRsp> {
|
||||
return runCatching {
|
||||
requestUploadNtPic(file, md5, sha, name, width, height, chatType, sceneBuilder).getOrThrow()
|
||||
}.onFailure {
|
||||
if (retryCnt > 0) {
|
||||
return requestUploadNtPic(file, md5, sha, name, width, height, retryCnt - 1, chatType, sceneBuilder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun requestUploadNtPic(
|
||||
file: File,
|
||||
md5: String,
|
||||
sha: String,
|
||||
name: String,
|
||||
width: UInt,
|
||||
height: UInt,
|
||||
chatType: Int,
|
||||
sceneBuilder: suspend SceneInfo.() -> Unit
|
||||
): Result<UploadRsp> {
|
||||
val req = NtV2RichMediaReq(
|
||||
head = MultiMediaReqHead(
|
||||
commonHead = CommonHead(
|
||||
requestId = requestIdSeq.incrementAndGet().toULong(),
|
||||
cmd = 100u
|
||||
),
|
||||
sceneInfo = SceneInfo(
|
||||
requestType = 2u,
|
||||
businessType = 1u,
|
||||
).apply {
|
||||
sceneBuilder()
|
||||
},
|
||||
clientMeta = ClientMeta(2u)
|
||||
),
|
||||
upload = UploadReq(
|
||||
listOf(UploadInfo(
|
||||
FileInfo(
|
||||
fileSize = file.length().toULong(),
|
||||
md5 = md5,
|
||||
sha1 = sha,
|
||||
name = name,
|
||||
fileType = FileType(
|
||||
fileType = 1u,
|
||||
picFormat = 1000u,
|
||||
videoFormat = 0u,
|
||||
voiceFormat = 0u
|
||||
),
|
||||
width = width,
|
||||
height = height,
|
||||
time = 0u,
|
||||
original = 1u
|
||||
),
|
||||
subFileType = 0u
|
||||
)),
|
||||
tryFastUploadCompleted = true,
|
||||
srvSendMsg = false,
|
||||
clientRandomId = Random.nextULong(),
|
||||
compatQMsgSceneType = 2u,
|
||||
clientSeq = Random.nextUInt(),
|
||||
noNeedCompatMsg = true
|
||||
)
|
||||
).toByteArray()
|
||||
val fromServiceMsg = when (chatType) {
|
||||
MsgConstant.KCHATTYPEGROUP -> {
|
||||
sendOidbAW("OidbSvcTrpcTcp.0x11c4_100", 4548, 100, req, true, timeout = 3.seconds)
|
||||
}
|
||||
MsgConstant.KCHATTYPEC2C -> {
|
||||
sendOidbAW("OidbSvcTrpcTcp.0x11c5_100", 4549, 100, req, true, timeout = 3.seconds)
|
||||
}
|
||||
else -> return Result.failure(Exception("unknown chat type: $chatType"))
|
||||
}
|
||||
if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) {
|
||||
return Result.failure(Exception("unable to request upload nt pic"))
|
||||
}
|
||||
val trpc = fromServiceMsg.decodeToTrpcOidb()
|
||||
if (trpc.buffer == null) {
|
||||
return Result.failure(Exception("unable to request upload nt pic: ${trpc.msg}"))
|
||||
}
|
||||
val rsp = trpc.buffer!!.decodeProtobuf<NtV2RichMediaRsp>()
|
||||
if (rsp.upload == null) {
|
||||
return Result.failure(Exception("unable to request upload nt pic: ${rsp.head}"))
|
||||
}
|
||||
return Result.success(rsp.upload!!)
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用OldBDH获取图片上传状态以及图片上传服务器
|
||||
*/
|
||||
suspend fun requestUploadGroupPic(
|
||||
groupId: ULong,
|
||||
md5: String,
|
||||
fileSize: ULong,
|
||||
width: UInt,
|
||||
height: UInt,
|
||||
): Result<TryUpPicData> {
|
||||
return runCatching {
|
||||
val fromServiceMsg = sendBufferAW("ImgStore.GroupPicUp", true, Cmd0x388ReqBody(
|
||||
netType = 3,
|
||||
subCmd = 1,
|
||||
msgTryUpImg = arrayListOf(
|
||||
TryUpImgReq(
|
||||
groupCode = groupId.toLong(),
|
||||
srcUin = app.longAccountUin,
|
||||
fileMd5 = md5.hex2ByteArray(),
|
||||
fileSize = fileSize.toLong(),
|
||||
fileName = "$md5.jpg",
|
||||
srcTerm = 2,
|
||||
platformType = 9,
|
||||
buType = 212,
|
||||
picWidth = width.toInt(),
|
||||
picHeight = height.toInt(),
|
||||
picType = 1000,
|
||||
buildVer = "1.0.0",
|
||||
originalPic = 1,
|
||||
fileIndex = byteArrayOf(),
|
||||
srvUpload = 0
|
||||
)
|
||||
),
|
||||
).toByteArray())
|
||||
if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) {
|
||||
return Result.failure(Exception("unable to request upload group pic"))
|
||||
}
|
||||
val rsp = fromServiceMsg.wupBuffer.slice(4).decodeProtobuf<Cmd0x388RspBody>()
|
||||
.msgTryUpImgRsp!!.first()
|
||||
TryUpPicData(
|
||||
uKey = rsp.ukey,
|
||||
exist = rsp.fileExist,
|
||||
fileId = rsp.fileId.toULong(),
|
||||
upIp = rsp.upIp,
|
||||
upPort = rsp.upPort
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
58
xposed/src/main/java/qq/service/bdh/ResourceData.kt
Normal file
58
xposed/src/main/java/qq/service/bdh/ResourceData.kt
Normal file
@ -0,0 +1,58 @@
|
||||
package qq.service.bdh
|
||||
|
||||
import com.tencent.mobileqq.data.MessageRecord
|
||||
import java.io.File
|
||||
|
||||
internal enum class ContactType {
|
||||
TROOP,
|
||||
PRIVATE,
|
||||
}
|
||||
|
||||
internal interface TransTarget {
|
||||
val id: String
|
||||
val type: ContactType
|
||||
|
||||
val mRec: MessageRecord?
|
||||
}
|
||||
|
||||
internal class Troop(
|
||||
override val id: String,
|
||||
override val mRec: MessageRecord? = null
|
||||
): TransTarget {
|
||||
override val type: ContactType = ContactType.TROOP
|
||||
}
|
||||
|
||||
internal class Private(
|
||||
override val id: String,
|
||||
override val mRec: MessageRecord? = null
|
||||
): TransTarget {
|
||||
override val type: ContactType = ContactType.PRIVATE
|
||||
}
|
||||
|
||||
internal enum class ResourceType {
|
||||
Picture,
|
||||
Video,
|
||||
Voice
|
||||
}
|
||||
|
||||
internal interface Resource {
|
||||
val type: ResourceType
|
||||
}
|
||||
|
||||
internal data class PictureResource(
|
||||
val src: File
|
||||
): Resource {
|
||||
override val type = ResourceType.Picture
|
||||
}
|
||||
|
||||
internal data class VideoResource(
|
||||
val src: File, val thumb: File
|
||||
): Resource {
|
||||
override val type = ResourceType.Video
|
||||
}
|
||||
|
||||
internal data class VoiceResource(
|
||||
val src: File
|
||||
): Resource {
|
||||
override val type = ResourceType.Voice
|
||||
}
|
499
xposed/src/main/java/qq/service/bdh/RichProtoSvc.kt
Normal file
499
xposed/src/main/java/qq/service/bdh/RichProtoSvc.kt
Normal file
@ -0,0 +1,499 @@
|
||||
@file:OptIn(ExperimentalSerializationApi::class)
|
||||
package qq.service.bdh
|
||||
|
||||
import com.tencent.mobileqq.pb.ByteStringMicro
|
||||
import com.tencent.mobileqq.transfile.FileMsg
|
||||
import com.tencent.mobileqq.transfile.api.IProtoReqManager
|
||||
import com.tencent.mobileqq.transfile.protohandler.RichProto
|
||||
import com.tencent.mobileqq.transfile.protohandler.RichProtoProc
|
||||
import com.tencent.qqnt.kernel.nativeinterface.Image
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
|
||||
import com.tencent.qqnt.kernel.nativeinterface.PicElement
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToOidb
|
||||
import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
|
||||
import moe.fuqiuluo.shamrock.tools.toHexString
|
||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
||||
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||
import mqq.app.MobileQQ
|
||||
import protobuf.auto.toByteArray
|
||||
import protobuf.oidb.cmd0x11c5.C2CUserInfo
|
||||
import protobuf.oidb.cmd0x11c5.ChannelUserInfo
|
||||
import protobuf.oidb.cmd0x11c5.GroupUserInfo
|
||||
import protobuf.oidb.cmd0xfc2.Oidb0xfc2ChannelInfo
|
||||
import protobuf.oidb.cmd0xfc2.Oidb0xfc2MsgApplyDownloadReq
|
||||
import protobuf.oidb.cmd0xfc2.Oidb0xfc2ReqBody
|
||||
import protobuf.oidb.cmd0xfc2.Oidb0xfc2RspBody
|
||||
import qq.service.QQInterfaces
|
||||
import qq.service.contact.ContactHelper
|
||||
import tencent.im.cs.cmd0x346.cmd0x346
|
||||
import tencent.im.oidb.cmd0x6d6.oidb_0x6d6
|
||||
import tencent.im.oidb.cmd0xe37.cmd0xe37
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
private const val GPRO_PIC = "gchat.qpic.cn"
|
||||
private const val MULTIMEDIA_DOMAIN = "multimedia.nt.qq.com.cn"
|
||||
private const val C2C_PIC = "c2cpicdw.qpic.cn"
|
||||
|
||||
internal object RichProtoSvc: QQInterfaces() {
|
||||
suspend fun getGuildFileDownUrl(peerId: String, channelId: String, fileId: String, bizId: Int): String {
|
||||
val fromServiceMsg = sendOidbAW("OidbSvcTrpcTcp.0xfc2_0", 4034, 0, Oidb0xfc2ReqBody(
|
||||
msgCmd = 1200,
|
||||
msgBusType = 4202,
|
||||
msgChannelInfo = Oidb0xfc2ChannelInfo(
|
||||
guildId = peerId.toULong(),
|
||||
channelId = channelId.toULong()
|
||||
),
|
||||
msgTerminalType = 2,
|
||||
msgApplyDownloadReq = Oidb0xfc2MsgApplyDownloadReq(
|
||||
fieldId = fileId,
|
||||
supportEncrypt = 0
|
||||
)
|
||||
).toByteArray())
|
||||
if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) {
|
||||
return ""
|
||||
}
|
||||
val body = fromServiceMsg.decodeToOidb()
|
||||
body.bytes_bodybuffer
|
||||
.get().toByteArray()
|
||||
.decodeProtobuf<Oidb0xfc2RspBody>()
|
||||
.msgApplyDownloadRsp?.let {
|
||||
it.msgDownloadInfo?.let {
|
||||
return "https://${it.downloadDomain}${it.downloadUrl}&fname=$fileId&isthumb=0"
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
suspend fun getGroupFileDownUrl(
|
||||
peerId: Long,
|
||||
fileId: String,
|
||||
bizId: Int = 102
|
||||
): String {
|
||||
val fromServiceMsg = sendOidbAW("OidbSvcTrpcTcp.0x6d6_2", 1750, 2, oidb_0x6d6.ReqBody().apply {
|
||||
download_file_req.set(oidb_0x6d6.DownloadFileReqBody().apply {
|
||||
uint64_group_code.set(peerId)
|
||||
uint32_app_id.set(3)
|
||||
uint32_bus_id.set(bizId)
|
||||
str_file_id.set(fileId)
|
||||
})
|
||||
}.toByteArray())
|
||||
if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) {
|
||||
return ""
|
||||
}
|
||||
val body = fromServiceMsg.decodeToOidb()
|
||||
val result = oidb_0x6d6.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
||||
if (body.uint32_result.get() != 0
|
||||
|| result.download_file_rsp.int32_ret_code.get() != 0) {
|
||||
return ""
|
||||
}
|
||||
|
||||
val domain = if (!result.download_file_rsp.str_download_dns.has())
|
||||
("https://" + result.download_file_rsp.str_download_ip.get())
|
||||
else ("http://" + result.download_file_rsp.str_download_dns.get().toByteArray().decodeToString())
|
||||
val downloadUrl = result.download_file_rsp.bytes_download_url.get().toByteArray().toHexString()
|
||||
val appId = MobileQQ.getMobileQQ().appId
|
||||
val version = PlatformUtils.getQQVersion(MobileQQ.getContext())
|
||||
|
||||
return "$domain/ftn_handler/$downloadUrl/?fname=$fileId&client_proto=qq&client_appid=$appId&client_type=android&client_ver=$version&client_down_type=auto&client_aio_type=unk"
|
||||
}
|
||||
|
||||
suspend fun getC2CFileDownUrl(
|
||||
fileId: String,
|
||||
subId: String,
|
||||
retryCnt: Int = 0
|
||||
): String {
|
||||
val fromServiceMsg = sendOidbAW("OidbSvc.0xe37_1200", 3639, 1200, cmd0xe37.Req0xe37().apply {
|
||||
bytes_cmd_0x346_req_body.set(ByteStringMicro.copyFrom(cmd0x346.ReqBody().apply {
|
||||
uint32_cmd.set(1200)
|
||||
uint32_seq.set(1)
|
||||
msg_apply_download_req.set(cmd0x346.ApplyDownloadReq().apply {
|
||||
uint64_uin.set(app.longAccountUin)
|
||||
bytes_uuid.set(ByteStringMicro.copyFrom(fileId.toByteArray()))
|
||||
uint32_owner_type.set(2)
|
||||
str_fileidcrc.set(subId)
|
||||
|
||||
})
|
||||
uint32_business_id.set(3)
|
||||
uint32_client_type.set(104)
|
||||
uint32_flag_support_mediaplatform.set(1)
|
||||
msg_extension_req.set(cmd0x346.ExtensionReq().apply {
|
||||
uint32_download_url_type.set(1)
|
||||
})
|
||||
}.toByteArray()))
|
||||
}.toByteArray())
|
||||
if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) {
|
||||
if (retryCnt < 5) {
|
||||
return getC2CFileDownUrl(fileId, subId, retryCnt + 1)
|
||||
}
|
||||
return ""
|
||||
} else {
|
||||
val body = fromServiceMsg.decodeToOidb()
|
||||
val result = cmd0x346.RspBody().mergeFrom(cmd0xe37.Resp0xe37().mergeFrom(
|
||||
body.bytes_bodybuffer.get().toByteArray()
|
||||
).bytes_cmd_0x346_rsp_body.get().toByteArray())
|
||||
if (body.uint32_result.get() != 0 ||
|
||||
result.msg_apply_download_rsp.int32_ret_code.has() && result.msg_apply_download_rsp.int32_ret_code.get() != 0) {
|
||||
return ""
|
||||
}
|
||||
|
||||
val oldData = result.msg_apply_download_rsp.msg_download_info
|
||||
//val newData = result[14, 40] NTQQ 文件信息
|
||||
|
||||
val domain = if (oldData.str_download_dns.has()) ("https://" + oldData.str_download_dns.get()) else ("http://" + oldData.rpt_str_downloadip_list.get().first())
|
||||
val params = oldData.str_download_url.get()
|
||||
val appId = MobileQQ.getMobileQQ().appId
|
||||
val version = PlatformUtils.getQQVersion(MobileQQ.getContext())
|
||||
|
||||
return "$domain$params&isthumb=0&client_proto=qq&client_appid=$appId&client_type=android&client_ver=$version&client_down_type=auto&client_aio_type=unk"
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getTempPicDownloadUrl(
|
||||
chatType: Int,
|
||||
originalUrl: String,
|
||||
md5: String,
|
||||
image: PicElement,
|
||||
storeId: Int = 0,
|
||||
peer: String? = null,
|
||||
subPeer: String? = null,
|
||||
): String {
|
||||
val isNtServer = originalUrl.startsWith("/download")
|
||||
if (isNtServer) {
|
||||
val tmpRKey = NtV2RichMediaSvc.getTempNtRKey()
|
||||
if (tmpRKey.isSuccess) {
|
||||
val tmpRKeyRsp = tmpRKey.getOrThrow()
|
||||
val tmpRKeyMap = hashMapOf<UInt, String>()
|
||||
tmpRKeyRsp.rkeys?.forEach { rKeyInfo ->
|
||||
tmpRKeyMap[rKeyInfo.type] = rKeyInfo.rkey
|
||||
}
|
||||
val rkey = tmpRKeyMap[when(chatType) {
|
||||
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> 10u
|
||||
MsgConstant.KCHATTYPEC2C -> 20u
|
||||
MsgConstant.KCHATTYPEGUILD -> 10u
|
||||
else -> 0u
|
||||
}]
|
||||
if (rkey != null) {
|
||||
return "https://$MULTIMEDIA_DOMAIN$originalUrl$rkey"
|
||||
}
|
||||
}
|
||||
}
|
||||
return when (chatType) {
|
||||
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> getGroupPicDownUrl(
|
||||
originalUrl = originalUrl,
|
||||
md5 = md5,
|
||||
fileId = image.fileUuid,
|
||||
width = image.picWidth.toUInt(),
|
||||
height = image.picHeight.toUInt(),
|
||||
sha = "",
|
||||
fileSize = image.fileSize.toULong(),
|
||||
peer = peer ?: "0"
|
||||
)
|
||||
|
||||
MsgConstant.KCHATTYPEC2C -> getC2CPicDownUrl(
|
||||
originalUrl = originalUrl,
|
||||
md5 = md5,
|
||||
fileId = image.fileUuid,
|
||||
width = image.picWidth.toUInt(),
|
||||
height = image.picHeight.toUInt(),
|
||||
sha = "",
|
||||
fileSize = image.fileSize.toULong(),
|
||||
peer = peer ?: "0",
|
||||
storeId = storeId
|
||||
)
|
||||
|
||||
MsgConstant.KCHATTYPEGUILD -> getGuildPicDownUrl(
|
||||
originalUrl = originalUrl,
|
||||
md5 = md5,
|
||||
fileId = image.fileUuid,
|
||||
width = image.picWidth.toUInt(),
|
||||
height = image.picHeight.toUInt(),
|
||||
sha = "",
|
||||
fileSize = image.fileSize.toULong(),
|
||||
peer = peer ?: "0",
|
||||
subPeer = subPeer ?: "0"
|
||||
)
|
||||
|
||||
else -> throw UnsupportedOperationException("Not supported chat type: $chatType")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getGroupPicDownUrl(
|
||||
originalUrl: String,
|
||||
md5: String,
|
||||
peer: String = "",
|
||||
fileId: String = "",
|
||||
sha: String = "",
|
||||
fileSize: ULong = 0uL,
|
||||
width: UInt = 0u,
|
||||
height: UInt = 0u
|
||||
): String {
|
||||
val isNtServer = originalUrl.startsWith("/download")
|
||||
val domain = if (isNtServer) MULTIMEDIA_DOMAIN else GPRO_PIC
|
||||
if (originalUrl.isNotEmpty()) {
|
||||
if (isNtServer && !originalUrl.contains("rkey=")) {
|
||||
NtV2RichMediaSvc.getNtPicRKey(
|
||||
fileId = fileId,
|
||||
md5 = md5,
|
||||
sha = sha,
|
||||
fileSize = fileSize,
|
||||
width = width,
|
||||
height = height
|
||||
) {
|
||||
sceneType = 2u
|
||||
grp = GroupUserInfo(peer.toULong())
|
||||
}.onSuccess {
|
||||
return "https://$domain$originalUrl$it"
|
||||
}.onFailure {
|
||||
LogCenter.log("getGroupPicDownUrl: ${it.stackTraceToString()}", Level.WARN)
|
||||
}
|
||||
}
|
||||
return "https://$domain$originalUrl"
|
||||
}
|
||||
return "https://$domain/gchatpic_new/0/0-0-${md5.uppercase()}/0?term=2"
|
||||
}
|
||||
|
||||
suspend fun getC2CPicDownUrl(
|
||||
originalUrl: String,
|
||||
md5: String,
|
||||
peer: String = "",
|
||||
fileId: String = "",
|
||||
sha: String = "",
|
||||
fileSize: ULong = 0uL,
|
||||
width: UInt = 0u,
|
||||
height: UInt = 0u,
|
||||
storeId: Int = 0
|
||||
): String {
|
||||
val isNtServer = storeId == 1 || originalUrl.startsWith("/download")
|
||||
val domain = if (isNtServer) MULTIMEDIA_DOMAIN else C2C_PIC
|
||||
if (originalUrl.isNotEmpty()) {
|
||||
if (fileId.isNotEmpty()) NtV2RichMediaSvc.getNtPicRKey(
|
||||
fileId = fileId,
|
||||
md5 = md5,
|
||||
sha = sha,
|
||||
fileSize = fileSize,
|
||||
width = width,
|
||||
height = height
|
||||
) {
|
||||
sceneType = 1u
|
||||
c2c = C2CUserInfo(
|
||||
accountType = 2u,
|
||||
uid = ContactHelper.getUidByUinAsync(peer.toLong())
|
||||
)
|
||||
}.onSuccess {
|
||||
if (isNtServer && !originalUrl.contains("rkey=")) {
|
||||
return "https://$domain$originalUrl$it"
|
||||
}
|
||||
}.onFailure {
|
||||
LogCenter.log("getC2CPicDownUrl: ${it.stackTraceToString()}", Level.WARN)
|
||||
}
|
||||
if (isNtServer && !originalUrl.contains("rkey=")) {
|
||||
return "https://$domain$originalUrl&rkey="
|
||||
}
|
||||
return "https://$domain$originalUrl"
|
||||
}
|
||||
return "https://$domain/offpic_new/0/0-0-${md5}/0?term=2"
|
||||
}
|
||||
|
||||
suspend fun getGuildPicDownUrl(
|
||||
originalUrl: String,
|
||||
md5: String,
|
||||
peer: String = "",
|
||||
subPeer: String = "",
|
||||
fileId: String = "",
|
||||
sha: String = "",
|
||||
fileSize: ULong = 0uL,
|
||||
width: UInt = 0u,
|
||||
height: UInt = 0u
|
||||
): String {
|
||||
val isNtServer = originalUrl.startsWith("/download")
|
||||
val domain = if (isNtServer) MULTIMEDIA_DOMAIN else GPRO_PIC
|
||||
if (originalUrl.isNotEmpty()) {
|
||||
if (isNtServer && !originalUrl.contains("rkey=")) {
|
||||
NtV2RichMediaSvc.getNtPicRKey(
|
||||
fileId = fileId,
|
||||
md5 = md5,
|
||||
sha = sha,
|
||||
fileSize = fileSize,
|
||||
width = width,
|
||||
height = height
|
||||
) {
|
||||
sceneType = 3u
|
||||
channel = ChannelUserInfo(peer.toULong(), subPeer.toULong(), 1u)
|
||||
}.onSuccess {
|
||||
return "https://$domain$originalUrl$it"
|
||||
}.onFailure {
|
||||
LogCenter.log("getGuildPicDownUrl: ${it.stackTraceToString()}", Level.WARN)
|
||||
}
|
||||
return "https://$domain$originalUrl&rkey="
|
||||
}
|
||||
return "https://$domain$originalUrl"
|
||||
}
|
||||
return "https://$domain/qmeetpic/0/0-0-${md5.uppercase()}/0?term=2"
|
||||
}
|
||||
|
||||
suspend fun getC2CVideoDownUrl(
|
||||
peerId: String,
|
||||
md5: ByteArray,
|
||||
fileUUId: String
|
||||
): String {
|
||||
return suspendCancellableCoroutine {
|
||||
val richProtoReq = RichProto.RichProtoReq()
|
||||
val downReq: RichProto.RichProtoReq.ShortVideoDownReq = RichProto.RichProtoReq.ShortVideoDownReq()
|
||||
downReq.selfUin = app.currentAccountUin
|
||||
downReq.peerUin = peerId
|
||||
downReq.secondUin = peerId
|
||||
downReq.uinType = FileMsg.UIN_BUDDY
|
||||
downReq.agentType = 0
|
||||
downReq.chatType = 1
|
||||
downReq.troopUin = peerId
|
||||
downReq.clientType = 2
|
||||
downReq.fileId = fileUUId
|
||||
downReq.md5 = md5
|
||||
downReq.busiType = FileTransfer.BUSI_TYPE_SHORT_VIDEO
|
||||
downReq.subBusiType = 0
|
||||
downReq.fileType = FileTransfer.VIDEO_FORMAT_MP4
|
||||
downReq.downType = 1
|
||||
downReq.sceneType = 1
|
||||
richProtoReq.callback = RichProtoProc.RichProtoCallback { _, resp ->
|
||||
if (resp.resps.isEmpty() || resp.resps.first().errCode != 0) {
|
||||
LogCenter.log("requestDownPrivateVideo: ${resp.resps.firstOrNull()?.errCode}", Level.WARN)
|
||||
it.resume("")
|
||||
} else {
|
||||
val videoDownResp = resp.resps.first() as RichProto.RichProtoResp.ShortVideoDownResp
|
||||
val url = StringBuilder()
|
||||
url.append(videoDownResp.mIpList.random().getServerUrl("http://"))
|
||||
url.append(videoDownResp.mUrl)
|
||||
it.resume(url.toString())
|
||||
}
|
||||
}
|
||||
richProtoReq.protoKey = RichProtoProc.SHORT_VIDEO_DW
|
||||
richProtoReq.reqs.add(downReq)
|
||||
richProtoReq.protoReqMgr = app.getRuntimeService(IProtoReqManager::class.java, "all")
|
||||
RichProtoProc.procRichProtoReq(richProtoReq)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getGroupVideoDownUrl(
|
||||
peerId: String,
|
||||
md5: ByteArray,
|
||||
fileUUId: String
|
||||
): String {
|
||||
return suspendCancellableCoroutine {
|
||||
val richProtoReq = RichProto.RichProtoReq()
|
||||
val downReq: RichProto.RichProtoReq.ShortVideoDownReq = RichProto.RichProtoReq.ShortVideoDownReq()
|
||||
downReq.selfUin = app.currentAccountUin
|
||||
downReq.peerUin = peerId
|
||||
downReq.secondUin = peerId
|
||||
downReq.uinType = FileMsg.UIN_TROOP
|
||||
downReq.agentType = 0
|
||||
downReq.chatType = 1
|
||||
downReq.troopUin = peerId
|
||||
downReq.clientType = 2
|
||||
downReq.fileId = fileUUId
|
||||
downReq.md5 = md5
|
||||
downReq.busiType = FileTransfer.BUSI_TYPE_SHORT_VIDEO
|
||||
downReq.subBusiType = 0
|
||||
downReq.fileType = FileTransfer.VIDEO_FORMAT_MP4
|
||||
downReq.downType = 1
|
||||
downReq.sceneType = 1
|
||||
richProtoReq.callback = RichProtoProc.RichProtoCallback { _, resp ->
|
||||
if (resp.resps.isEmpty() || resp.resps.first().errCode != 0) {
|
||||
LogCenter.log("requestDownGroupVideo: ${resp.resps.firstOrNull()?.errCode}", Level.WARN)
|
||||
it.resume("")
|
||||
} else {
|
||||
val videoDownResp = resp.resps.first() as RichProto.RichProtoResp.ShortVideoDownResp
|
||||
val url = StringBuilder()
|
||||
url.append(videoDownResp.mIpList.random().getServerUrl("http://"))
|
||||
url.append(videoDownResp.mUrl)
|
||||
it.resume(url.toString())
|
||||
}
|
||||
}
|
||||
richProtoReq.protoKey = RichProtoProc.SHORT_VIDEO_DW
|
||||
richProtoReq.reqs.add(downReq)
|
||||
richProtoReq.protoReqMgr = app.getRuntimeService(IProtoReqManager::class.java, "all")
|
||||
RichProtoProc.procRichProtoReq(richProtoReq)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getC2CPttDownUrl(
|
||||
peerId: String,
|
||||
fileUUId: String
|
||||
): String {
|
||||
return suspendCancellableCoroutine {
|
||||
val richProtoReq = RichProto.RichProtoReq()
|
||||
val pttDownReq: RichProto.RichProtoReq.C2CPttDownReq = RichProto.RichProtoReq.C2CPttDownReq()
|
||||
pttDownReq.selfUin = app.currentAccountUin
|
||||
pttDownReq.peerUin = peerId
|
||||
pttDownReq.secondUin = peerId
|
||||
pttDownReq.uinType = FileMsg.UIN_BUDDY
|
||||
pttDownReq.busiType = 1002
|
||||
pttDownReq.uuid = fileUUId
|
||||
pttDownReq.storageSource = "pttcenter"
|
||||
pttDownReq.isSelfSend = false
|
||||
|
||||
pttDownReq.voiceType = 1
|
||||
pttDownReq.downType = 1
|
||||
richProtoReq.callback = RichProtoProc.RichProtoCallback { _, resp ->
|
||||
if (resp.resps.isEmpty() || resp.resps.first().errCode != 0) {
|
||||
LogCenter.log("requestDownPrivateVoice: ${resp.resps.firstOrNull()?.errCode}", Level.WARN)
|
||||
it.resume("")
|
||||
} else {
|
||||
val pttDownResp = resp.resps.first() as RichProto.RichProtoResp.C2CPttDownResp
|
||||
val url = StringBuilder()
|
||||
url.append(pttDownResp.downloadUrl)
|
||||
url.append("&client_proto=qq&client_appid=${MobileQQ.getMobileQQ().appId}&client_type=android&client_ver=${PlatformUtils.getQQVersion(MobileQQ.getContext())}&client_down_type=auto&client_aio_type=unk")
|
||||
it.resume(url.toString())
|
||||
}
|
||||
}
|
||||
richProtoReq.protoKey = RichProtoProc.C2C_PTT_DW
|
||||
richProtoReq.reqs.add(pttDownReq)
|
||||
richProtoReq.protoReqMgr = app.getRuntimeService(IProtoReqManager::class.java, "all")
|
||||
RichProtoProc.procRichProtoReq(richProtoReq)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getGroupPttDownUrl(
|
||||
peerId: String,
|
||||
md5: ByteArray,
|
||||
groupFileKey: String
|
||||
): String {
|
||||
return suspendCancellableCoroutine {
|
||||
val richProtoReq = RichProto.RichProtoReq()
|
||||
val groupPttDownReq: RichProto.RichProtoReq.GroupPttDownReq = RichProto.RichProtoReq.GroupPttDownReq()
|
||||
groupPttDownReq.selfUin = app.currentAccountUin
|
||||
groupPttDownReq.peerUin = peerId
|
||||
groupPttDownReq.secondUin = peerId
|
||||
groupPttDownReq.uinType = FileMsg.UIN_TROOP
|
||||
groupPttDownReq.groupFileID = 0
|
||||
groupPttDownReq.groupFileKey = groupFileKey
|
||||
groupPttDownReq.md5 = md5
|
||||
groupPttDownReq.voiceType = 1
|
||||
groupPttDownReq.downType = 1
|
||||
richProtoReq.callback = RichProtoProc.RichProtoCallback { _, resp ->
|
||||
if (resp.resps.isEmpty() || resp.resps.first().errCode != 0) {
|
||||
LogCenter.log("requestDownGroupVoice: ${resp.resps.firstOrNull()?.errCode}", Level.WARN)
|
||||
it.resume("")
|
||||
} else {
|
||||
val pttDownResp = resp.resps.first() as RichProto.RichProtoResp.GroupPttDownResp
|
||||
val url = StringBuilder()
|
||||
url.append("http://")
|
||||
url.append(pttDownResp.domainV4V6)
|
||||
url.append(pttDownResp.urlPath)
|
||||
url.append("&client_proto=qq&client_appid=${MobileQQ.getMobileQQ().appId}&client_type=android&client_ver=${
|
||||
PlatformUtils.getQQVersion(
|
||||
MobileQQ.getContext())}&client_down_type=auto&client_aio_type=unk")
|
||||
it.resume(url.toString())
|
||||
}
|
||||
}
|
||||
richProtoReq.protoKey = RichProtoProc.GRP_PTT_DW
|
||||
richProtoReq.reqs.add(groupPttDownReq)
|
||||
richProtoReq.protoReqMgr = app.getRuntimeService(IProtoReqManager::class.java, "all")
|
||||
RichProtoProc.procRichProtoReq(richProtoReq)
|
||||
}
|
||||
}
|
||||
}
|
135
xposed/src/main/java/qq/service/bdh/Transfer.kt
Normal file
135
xposed/src/main/java/qq/service/bdh/Transfer.kt
Normal file
@ -0,0 +1,135 @@
|
||||
package qq.service.bdh
|
||||
|
||||
import com.tencent.mobileqq.data.MessageForShortVideo
|
||||
import com.tencent.mobileqq.data.MessageRecord
|
||||
import com.tencent.mobileqq.transfile.FileMsg
|
||||
import com.tencent.mobileqq.transfile.TransferRequest
|
||||
import moe.fuqiuluo.shamrock.utils.MD5
|
||||
import qq.service.bdh.ResourceType.*
|
||||
import java.io.File
|
||||
|
||||
internal object Transfer: FileTransfer() {
|
||||
private val ROUTE = mapOf<ContactType, Map<ResourceType, suspend TransTarget.(Resource) -> Boolean>>(
|
||||
ContactType.TROOP to mapOf(
|
||||
Picture to { uploadGroupPic(id, (it as PictureResource).src, mRec) },
|
||||
Voice to { uploadGroupVoice(id, (it as VoiceResource).src) },
|
||||
Video to { uploadGroupVideo(id, (it as VideoResource).src, it.thumb) },
|
||||
|
||||
),
|
||||
ContactType.PRIVATE to mapOf(
|
||||
Picture to { uploadC2CPic(id, (it as PictureResource).src, mRec) },
|
||||
Voice to { uploadC2CVoice(id, (it as VoiceResource).src) },
|
||||
Video to { uploadC2CVideo(id, (it as VideoResource).src, it.thumb) },
|
||||
)
|
||||
)
|
||||
|
||||
suspend fun uploadC2CVideo(
|
||||
userId: String,
|
||||
file: File,
|
||||
thumb: File,
|
||||
wait: Boolean = true
|
||||
): Boolean {
|
||||
return transC2CResource(userId, file, FileMsg.TRANSFILE_TYPE_SHORT_VIDEO_C2C, BUSI_TYPE_SHORT_VIDEO, wait) {
|
||||
it.mSourceVideoCodecFormat = VIDEO_FORMAT_MP4
|
||||
it.mRec = MessageForShortVideo().also {
|
||||
it.busiType = BUSI_TYPE_SHORT_VIDEO
|
||||
}
|
||||
it.mThumbPath = thumb.absolutePath
|
||||
it.mThumbMd5 = MD5.genFileMd5Hex(thumb.absolutePath)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun uploadGroupVideo(
|
||||
groupId: String,
|
||||
file: File,
|
||||
thumb: File,
|
||||
wait: Boolean = true
|
||||
): Boolean {
|
||||
return transTroopResource(groupId, file, FileMsg.TRANSFILE_TYPE_SHORT_VIDEO_TROOP, BUSI_TYPE_SHORT_VIDEO, wait) {
|
||||
it.mSourceVideoCodecFormat = VIDEO_FORMAT_MP4
|
||||
it.mRec = MessageForShortVideo().also {
|
||||
it.busiType = BUSI_TYPE_SHORT_VIDEO
|
||||
}
|
||||
it.mThumbPath = thumb.absolutePath
|
||||
it.mThumbMd5 = MD5.genFileMd5Hex(thumb.absolutePath)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun uploadC2CVoice(
|
||||
userId: String,
|
||||
file: File,
|
||||
wait: Boolean = true
|
||||
): Boolean {
|
||||
return transC2CResource(userId, file, FileMsg.TRANSFILE_TYPE_PTT, 1002, wait) {
|
||||
it.mPttUploadPanel = 3
|
||||
it.mPttCompressFinish = true
|
||||
it.mIsPttPreSend = true
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun uploadGroupVoice(
|
||||
groupId: String,
|
||||
file: File,
|
||||
wait: Boolean = true
|
||||
): Boolean {
|
||||
return transTroopResource(groupId, file, FileMsg.TRANSFILE_TYPE_PTT, 1002, wait) {
|
||||
it.mPttUploadPanel = 3
|
||||
it.mPttCompressFinish = true
|
||||
it.mIsPttPreSend = true
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun uploadC2CPic(
|
||||
peerId: String,
|
||||
file: File,
|
||||
record: MessageRecord? = null,
|
||||
wait: Boolean = true
|
||||
): Boolean {
|
||||
return transC2CResource(peerId, file, FileMsg.TRANSFILE_TYPE_PIC, SEND_MSG_BUSINESS_TYPE_PIC_CAMERA, wait) {
|
||||
val picUpExtraInfo = TransferRequest.PicUpExtraInfo()
|
||||
picUpExtraInfo.mIsRaw = false
|
||||
picUpExtraInfo.mUinType = FileMsg.UIN_BUDDY
|
||||
it.mPicSendSource = 8
|
||||
it.mExtraObj = picUpExtraInfo
|
||||
it.mIsPresend = true
|
||||
it.delayShowProgressTimeInMs = 2000
|
||||
it.mRec = record
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun uploadGroupPic(
|
||||
groupId: String,
|
||||
file: File,
|
||||
record: MessageRecord? = null,
|
||||
wait: Boolean = true
|
||||
): Boolean {
|
||||
return transTroopResource(groupId, file, FileMsg.TRANSFILE_TYPE_PIC, SEND_MSG_BUSINESS_TYPE_PIC_CAMERA, wait) {
|
||||
val picUpExtraInfo = TransferRequest.PicUpExtraInfo()
|
||||
picUpExtraInfo.mIsRaw = false
|
||||
picUpExtraInfo.mUinType = FileMsg.UIN_TROOP
|
||||
it.mPicSendSource = 8
|
||||
it.delayShowProgressTimeInMs = 2000
|
||||
it.mExtraObj = picUpExtraInfo
|
||||
it.mRec = record
|
||||
}
|
||||
}
|
||||
|
||||
operator fun get(contactType: ContactType, resourceType: ResourceType): suspend TransTarget.(Resource) -> Boolean {
|
||||
return (ROUTE[contactType] ?: error("unsupported contact type: $contactType"))[resourceType]
|
||||
?: error("Unsupported resource type: $resourceType")
|
||||
}
|
||||
}
|
||||
|
||||
internal suspend infix fun TransferTaskBuilder.trans(res: Resource): Boolean {
|
||||
return Transfer[contact.type, res.type](contact, res)
|
||||
}
|
||||
|
||||
internal class TransferTaskBuilder {
|
||||
lateinit var contact: TransTarget
|
||||
}
|
||||
|
||||
internal infix fun Transfer.with(contact: TransTarget): TransferTaskBuilder {
|
||||
return TransferTaskBuilder().also {
|
||||
it.contact = contact
|
||||
}
|
||||
}
|
13
xposed/src/main/java/qq/service/bdh/TryUpPicData.kt
Normal file
13
xposed/src/main/java/qq/service/bdh/TryUpPicData.kt
Normal file
@ -0,0 +1,13 @@
|
||||
package qq.service.bdh
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class TryUpPicData(
|
||||
@SerialName("ukey") val uKey: ByteArray,
|
||||
@SerialName("exist") val exist: Boolean,
|
||||
@SerialName("file_id") val fileId: ULong,
|
||||
@SerialName("up_ip") var upIp: ArrayList<Long>? = null,
|
||||
@SerialName("up_port") var upPort: ArrayList<Int>? = null,
|
||||
)
|
21
xposed/src/main/java/qq/service/contact/ContactExt.kt
Normal file
21
xposed/src/main/java/qq/service/contact/ContactExt.kt
Normal file
@ -0,0 +1,21 @@
|
||||
package qq.service.contact
|
||||
|
||||
import com.tencent.qqnt.kernelpublic.nativeinterface.Contact
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||
import io.kritor.common.Scene
|
||||
|
||||
suspend fun Contact.longPeer(): Long {
|
||||
return when(this.chatType) {
|
||||
MsgConstant.KCHATTYPEGROUP -> peerUid.toLong()
|
||||
MsgConstant.KCHATTYPETEMPC2CFROMGROUP, MsgConstant.KCHATTYPEC2C -> if (peerUid.startsWith("u_")) ContactHelper.getUinByUidAsync(peerUid).toLong() else peerUid.toLong()
|
||||
else -> 0L
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun io.kritor.common.Contact.longPeer(): Long {
|
||||
return when(this.scene) {
|
||||
Scene.GROUP -> peer.toLong()
|
||||
Scene.FRIEND, Scene.STRANGER, Scene.STRANGER_FROM_GROUP -> if (peer.startsWith("u_")) ContactHelper.getUinByUidAsync(peer).toLong() else peer.toLong()
|
||||
else -> 0L
|
||||
}
|
||||
}
|
@ -7,11 +7,16 @@ import com.tencent.mobileqq.profilecard.api.IProfileProtocolConst.PARAM_SELF_UIN
|
||||
import com.tencent.mobileqq.profilecard.api.IProfileProtocolConst.PARAM_TARGET_UIN
|
||||
import com.tencent.mobileqq.profilecard.api.IProfileProtocolService
|
||||
import com.tencent.mobileqq.profilecard.observer.ProfileCardObserver
|
||||
import com.tencent.protofile.join_group_link.join_group_link
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToOidb
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import qq.service.internals.NTServiceFetcher
|
||||
import qq.service.QQInterfaces
|
||||
import tencent.im.oidb.cmd0x11b2.oidb_0x11b2
|
||||
import tencent.im.oidb.oidb_sso
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
internal object ContactHelper: QQInterfaces() {
|
||||
@ -177,4 +182,30 @@ internal object ContactHelper: QQInterfaces() {
|
||||
}
|
||||
}[peerId]!!
|
||||
}
|
||||
|
||||
suspend fun getSharePrivateArkMsg(peerId: Long): String {
|
||||
val reqBody = oidb_0x11b2.BusinessCardV3Req()
|
||||
reqBody.uin.set(peerId)
|
||||
reqBody.jump_url.set("mqqapi://card/show_pslcard?src_type=internal&source=sharecard&version=1&uin=$peerId")
|
||||
|
||||
val fromServiceMsg = sendOidbAW("OidbSvcTrpcTcp.0x11ca_0", 4790, 0, reqBody.toByteArray())
|
||||
?: error("unable to fetch contact ark_json_text")
|
||||
|
||||
val body = fromServiceMsg.decodeToOidb()
|
||||
val rsp = oidb_0x11b2.BusinessCardV3Rsp()
|
||||
rsp.mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
||||
return rsp.signed_ark_msg.get()
|
||||
}
|
||||
|
||||
suspend fun getShareTroopArkMsg(groupId: Long): String {
|
||||
val reqBody = join_group_link.ReqBody()
|
||||
reqBody.get_ark.set(true)
|
||||
reqBody.type.set(1)
|
||||
reqBody.group_code.set(groupId)
|
||||
val fromServiceMsg = sendBufferAW("GroupSvc.JoinGroupLink", true, reqBody.toByteArray())
|
||||
?: error("unable to fetch contact ark_json_text")
|
||||
val body = join_group_link.RspBody()
|
||||
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
return body.signed_ark.get().toStringUtf8()
|
||||
}
|
||||
}
|
@ -1,23 +1,15 @@
|
||||
@file:OptIn(ExperimentalStdlibApi::class)
|
||||
|
||||
package qq.service.file
|
||||
|
||||
import com.tencent.mobileqq.pb.ByteStringMicro
|
||||
import io.grpc.Status
|
||||
import io.grpc.StatusRuntimeException
|
||||
import io.kritor.file.File
|
||||
import io.kritor.file.Folder
|
||||
import io.kritor.file.GetFileSystemInfoResponse
|
||||
import io.kritor.file.GetFilesRequest
|
||||
import io.kritor.file.GetFilesResponse
|
||||
import io.kritor.file.folder
|
||||
import io.kritor.file.getFileSystemInfoResponse
|
||||
import io.kritor.file.getFilesRequest
|
||||
import io.kritor.file.getFilesResponse
|
||||
import io.kritor.file.*
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToOidb
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import moe.fuqiuluo.shamrock.tools.toHexString
|
||||
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
||||
import qq.service.QQInterfaces
|
||||
import tencent.im.oidb.cmd0x6d8.oidb_0x6d8
|
||||
@ -33,18 +25,15 @@ internal object GroupFileHelper: QQInterfaces() {
|
||||
it.uint32_bus_id.set(0)
|
||||
})
|
||||
}.toByteArray()) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
|
||||
if (!fromServiceMsg.isSuccess) {
|
||||
if (fromServiceMsg.wupBuffer == null) {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
||||
}
|
||||
val fileCnt: Int
|
||||
val limitCnt: Int
|
||||
if (fromServiceMsg.wupBuffer != null) {
|
||||
oidb_0x6d8.RspBody().mergeFrom(
|
||||
oidb_sso.OIDBSSOPkg()
|
||||
.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
.bytes_bodybuffer.get()
|
||||
.toByteArray()
|
||||
).group_file_cnt_rsp.apply {
|
||||
val oidb1 = fromServiceMsg.decodeToOidb()
|
||||
|
||||
oidb_0x6d8.RspBody().mergeFrom(oidb1.bytes_bodybuffer.get().toByteArray()).group_file_cnt_rsp.apply {
|
||||
fileCnt = uint32_all_file_count.get()
|
||||
limitCnt = uint32_limit_count.get()
|
||||
}
|
||||
@ -61,11 +50,9 @@ internal object GroupFileHelper: QQInterfaces() {
|
||||
val totalSpace: Long
|
||||
val usedSpace: Long
|
||||
if (fromServiceMsg2.isSuccess && fromServiceMsg2.wupBuffer != null) {
|
||||
oidb_0x6d8.RspBody().mergeFrom(
|
||||
oidb_sso.OIDBSSOPkg()
|
||||
.mergeFrom(fromServiceMsg2.wupBuffer.slice(4))
|
||||
.bytes_bodybuffer.get()
|
||||
.toByteArray()).group_space_rsp.apply {
|
||||
val oidb2 = fromServiceMsg2.decodeToOidb()
|
||||
|
||||
oidb_0x6d8.RspBody().mergeFrom(oidb2.bytes_bodybuffer.get().toByteArray()).group_space_rsp.apply {
|
||||
totalSpace = uint64_total_space.get()
|
||||
usedSpace = uint64_used_space.get()
|
||||
}
|
||||
@ -73,15 +60,15 @@ internal object GroupFileHelper: QQInterfaces() {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to fetch oidb response x2"))
|
||||
}
|
||||
|
||||
return getFileSystemInfoResponse {
|
||||
return GetFileSystemInfoResponse.newBuilder().apply {
|
||||
this.fileCount = fileCnt
|
||||
this.totalCount = limitCnt
|
||||
this.totalSpace = totalSpace.toInt()
|
||||
this.usedSpace = usedSpace.toInt()
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
suspend fun getGroupFiles(groupId: Long, folderId: String = "/"): GetFilesResponse {
|
||||
suspend fun getGroupFiles(groupId: Long, folderId: String = "/"): GetFileListResponse {
|
||||
val fileSystemInfo = getGroupFileSystemInfo(groupId)
|
||||
val fromServiceMsg = sendOidbAW("OidbSvc.0x6d8_1", 1752, 1, oidb_0x6d8.ReqBody().also {
|
||||
it.file_list_info_req.set(oidb_0x6d8.GetFileListReqBody().apply {
|
||||
@ -103,48 +90,46 @@ internal object GroupFileHelper: QQInterfaces() {
|
||||
|
||||
uint32_show_onlinedoc_folder.set(0)
|
||||
})
|
||||
}.toByteArray(), timeout = 15.seconds) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
|
||||
if (!fromServiceMsg.isSuccess) {
|
||||
}.toByteArray(), timeout = 30.seconds) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
|
||||
if (fromServiceMsg.wupBuffer == null) {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
||||
}
|
||||
val files = arrayListOf<File>()
|
||||
val dirs = arrayListOf<Folder>()
|
||||
val folders = arrayListOf<Folder>()
|
||||
if (fromServiceMsg.wupBuffer != null) {
|
||||
val oidb = oidb_sso.OIDBSSOPkg().mergeFrom(fromServiceMsg.wupBuffer.slice(4).let {
|
||||
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
|
||||
})
|
||||
val oidb = fromServiceMsg.decodeToOidb()
|
||||
|
||||
oidb_0x6d8.RspBody().mergeFrom(oidb.bytes_bodybuffer.get().toByteArray())
|
||||
.file_list_info_rsp.apply {
|
||||
rpt_item_list.get().forEach { file ->
|
||||
if (file.uint32_type.get() == oidb_0x6d8.GetFileListRspBody.TYPE_FILE) {
|
||||
val fileInfo = file.file_info
|
||||
files.add(io.kritor.file.file {
|
||||
files.add(File.newBuilder().apply {
|
||||
this.fileId = fileInfo.str_file_id.get()
|
||||
this.fileName = fileInfo.str_file_name.get()
|
||||
this.fileSize = fileInfo.uint64_file_size.get()
|
||||
this.busId = fileInfo.uint32_bus_id.get()
|
||||
this.uploadTime = fileInfo.uint32_upload_time.get()
|
||||
this.deadTime = fileInfo.uint32_dead_time.get()
|
||||
this.modifyTime = fileInfo.uint32_modify_time.get()
|
||||
this.uploadTime = fileInfo.uint32_upload_time.get().toLong()
|
||||
this.expireTime = fileInfo.uint32_dead_time.get().toLong()
|
||||
this.modifyTime = fileInfo.uint32_modify_time.get().toLong()
|
||||
this.downloadTimes = fileInfo.uint32_download_times.get()
|
||||
this.uploader = fileInfo.uint64_uploader_uin.get()
|
||||
this.uploaderName = fileInfo.str_uploader_name.get()
|
||||
this.sha = fileInfo.bytes_sha.get().toByteArray().toHexString()
|
||||
this.sha3 = fileInfo.bytes_sha3.get().toByteArray().toHexString()
|
||||
this.md5 = fileInfo.bytes_md5.get().toByteArray().toHexString()
|
||||
})
|
||||
}.build())
|
||||
}
|
||||
else if (file.uint32_type.get() == oidb_0x6d8.GetFileListRspBody.TYPE_FOLDER) {
|
||||
val folderInfo = file.folder_info
|
||||
dirs.add(folder {
|
||||
folders.add(Folder.newBuilder().apply {
|
||||
this.folderId = folderInfo.str_folder_id.get()
|
||||
this.folderName = folderInfo.str_folder_name.get()
|
||||
this.totalFileCount = folderInfo.uint32_total_file_count.get()
|
||||
this.createTime = folderInfo.uint32_create_time.get()
|
||||
this.createTime = folderInfo.uint32_create_time.get().toLong()
|
||||
this.creator = folderInfo.uint64_create_uin.get()
|
||||
this.creatorName = folderInfo.str_creator_name.get()
|
||||
})
|
||||
}.build())
|
||||
} else {
|
||||
LogCenter.log("未知文件类型: ${file.uint32_type.get()}", Level.WARN)
|
||||
}
|
||||
@ -154,9 +139,9 @@ internal object GroupFileHelper: QQInterfaces() {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to fetch oidb response"))
|
||||
}
|
||||
|
||||
return getFilesResponse {
|
||||
this.files.addAll(files)
|
||||
this.folders.addAll(folders)
|
||||
}
|
||||
return GetFileListResponse.newBuilder().apply {
|
||||
this.addAllFiles(files)
|
||||
this.addAllFolders(folders)
|
||||
}.build()
|
||||
}
|
||||
}
|
@ -3,11 +3,15 @@ package qq.service.friend
|
||||
import com.tencent.mobileqq.data.Friends
|
||||
import com.tencent.mobileqq.friend.api.IFriendDataService
|
||||
import com.tencent.mobileqq.friend.api.IFriendHandlerService
|
||||
import com.tencent.mobileqq.qroute.QRoute
|
||||
import com.tencent.mobileqq.relation.api.IAddFriendTempApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import qq.service.QQInterfaces
|
||||
import tencent.mobileim.structmsg.structmsg
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
internal object FriendHelper: QQInterfaces() {
|
||||
@ -21,6 +25,69 @@ internal object FriendHelper: QQInterfaces() {
|
||||
return Result.success(service.allFriends)
|
||||
}
|
||||
|
||||
// ProfileService.Pb.ReqSystemMsgAction.Friend
|
||||
fun requestFriendRequest(msgSeq: Long, uin: Long, remark: String = "", approve: Boolean? = true, notSee: Boolean? = false) {
|
||||
val service = QRoute.api(IAddFriendTempApi::class.java)
|
||||
val action = structmsg.SystemMsgActionInfo()
|
||||
action.type.set(if (approve != false) 2 else 3)
|
||||
action.group_id.set(0)
|
||||
action.remark.set(remark)
|
||||
val snInfo = structmsg.AddFrdSNInfo()
|
||||
snInfo.uint32_not_see_dynamic.set(if (notSee != false) 1 else 0)
|
||||
snInfo.uint32_set_sn.set(0)
|
||||
action.addFrdSNInfo.set(snInfo)
|
||||
service.sendFriendSystemMsgAction(1, msgSeq, uin, 1, 2004, 11, 0, action, 0, structmsg.StructMsg(), false, app)
|
||||
}
|
||||
|
||||
suspend fun requestFriendSystemMsgNew(msgNum: Int, latestFriendSeq: Long = 0, latestGroupSeq: Long = 0, retryCnt: Int = 3): List<structmsg.StructMsg>? {
|
||||
if (retryCnt < 0) {
|
||||
return ArrayList()
|
||||
}
|
||||
val req = structmsg.ReqSystemMsgNew()
|
||||
req.msg_num.set(msgNum)
|
||||
req.latest_friend_seq.set(latestFriendSeq)
|
||||
req.latest_group_seq.set(latestGroupSeq)
|
||||
req.version.set(1000)
|
||||
req.checktype.set(2)
|
||||
val flag = structmsg.FlagInfo()
|
||||
// flag.GrpMsg_Kick_Admin.set(1)
|
||||
// flag.GrpMsg_HiddenGrp.set(1)
|
||||
// flag.GrpMsg_WordingDown.set(1)
|
||||
flag.FrdMsg_GetBusiCard.set(1)
|
||||
// flag.GrpMsg_GetOfficialAccount.set(1)
|
||||
// flag.GrpMsg_GetPayInGroup.set(1)
|
||||
flag.FrdMsg_Discuss2ManyChat.set(1)
|
||||
// flag.GrpMsg_NotAllowJoinGrp_InviteNotFrd.set(1)
|
||||
flag.FrdMsg_NeedWaitingMsg.set(1)
|
||||
flag.FrdMsg_uint32_need_all_unread_msg.set(1)
|
||||
// flag.GrpMsg_NeedAutoAdminWording.set(1)
|
||||
// flag.GrpMsg_get_transfer_group_msg_flag.set(1)
|
||||
// flag.GrpMsg_get_quit_pay_group_msg_flag.set(1)
|
||||
// flag.GrpMsg_support_invite_auto_join.set(1)
|
||||
// flag.GrpMsg_mask_invite_auto_join.set(1)
|
||||
// flag.GrpMsg_GetDisbandedByAdmin.set(1)
|
||||
flag.GrpMsg_GetC2cInviteJoinGroup.set(1)
|
||||
req.flag.set(flag)
|
||||
req.is_get_frd_ribbon.set(false)
|
||||
req.is_get_grp_ribbon.set(false)
|
||||
req.friend_msg_type_flag.set(1)
|
||||
req.uint32_req_msg_type.set(1)
|
||||
req.uint32_need_uid.set(1)
|
||||
val fromServiceMsg = sendBufferAW("ProfileService.Pb.ReqSystemMsgNew.Friend", true, req.toByteArray())
|
||||
return if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) {
|
||||
ArrayList()
|
||||
} else {
|
||||
try {
|
||||
val msg = structmsg.RspSystemMsgNew()
|
||||
msg.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
return msg.friendmsgs.get()
|
||||
} catch (err: Throwable) {
|
||||
requestFriendSystemMsgNew(msgNum, latestFriendSeq, latestGroupSeq, retryCnt - 1)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun requestFriendList(dataService: IFriendDataService): Boolean {
|
||||
val service = app.getRuntimeService(IFriendHandlerService::class.java, "all")
|
||||
service.requestFriendList(true, 0)
|
||||
|
@ -19,10 +19,13 @@ import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToOidb
|
||||
import qq.service.internals.NTServiceFetcher
|
||||
import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
|
||||
import moe.fuqiuluo.shamrock.tools.putBuf32Long
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils.QQ_9_0_65_VER
|
||||
import protobuf.auto.toByteArray
|
||||
import protobuf.oidb.cmd0xf16.Oidb0xf16
|
||||
import protobuf.oidb.cmd0xf16.SetGroupRemarkReq
|
||||
@ -37,10 +40,12 @@ import tencent.im.oidb.cmd0x8fc.Oidb_0x8fc
|
||||
import tencent.im.oidb.cmd0xed3.oidb_cmd0xed3
|
||||
import tencent.im.oidb.oidb_sso
|
||||
import tencent.im.troop.honor.troop_honor
|
||||
import tencent.mobileim.structmsg.structmsg
|
||||
import java.lang.reflect.Method
|
||||
import java.lang.reflect.Modifier
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
internal object GroupHelper: QQInterfaces() {
|
||||
private val RefreshTroopMemberInfoLock by lazy { Mutex() }
|
||||
@ -98,6 +103,61 @@ internal object GroupHelper: QQInterfaces() {
|
||||
return Result.success(troopList)
|
||||
}
|
||||
|
||||
suspend fun getTroopMemberInfoByUinV2(
|
||||
groupId: String,
|
||||
uin: String,
|
||||
refresh: Boolean = false
|
||||
): Result<TroopMemberInfo> {
|
||||
val service = app.getRuntimeService(ITroopMemberInfoService::class.java, "all")
|
||||
var info = service.getTroopMember(groupId, uin)
|
||||
if (refresh || !service.isMemberInCache(groupId, uin) || info == null || info.troopnick == null) {
|
||||
info = requestTroopMemberInfo(service, groupId, uin, timeout = 2000).getOrNull()
|
||||
}
|
||||
if (info == null) {
|
||||
info = getTroopMemberInfoByUinViaNt(groupId, uin, timeout = 2000L).getOrNull()?.let {
|
||||
TroopMemberInfo().apply {
|
||||
troopnick = it.cardName
|
||||
friendnick = it.nick
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (info != null && (info.alias == null || info.alias.isBlank())) {
|
||||
val req = group_member_info.ReqBody()
|
||||
req.uint64_group_code.set(groupId.toLong())
|
||||
req.uint64_uin.set(uin.toLong())
|
||||
req.bool_new_client.set(true)
|
||||
req.uint32_client_type.set(1)
|
||||
req.uint32_rich_card_name_ver.set(1)
|
||||
val fromServiceMsg = sendBufferAW("group_member_card.get_group_member_card_info", true, req.toByteArray(), timeout = 2.seconds)
|
||||
if (fromServiceMsg != null && fromServiceMsg.wupBuffer != null) {
|
||||
val rsp = group_member_info.RspBody()
|
||||
rsp.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
if (rsp.msg_meminfo.str_location.has()) {
|
||||
info.alias = rsp.msg_meminfo.str_location.get().toStringUtf8()
|
||||
}
|
||||
if (rsp.msg_meminfo.uint32_age.has()) {
|
||||
info.age = rsp.msg_meminfo.uint32_age.get().toByte()
|
||||
}
|
||||
if (rsp.msg_meminfo.bytes_group_honor.has()) {
|
||||
val honorBytes = rsp.msg_meminfo.bytes_group_honor.get().toByteArray()
|
||||
val honor = troop_honor.GroupUserCardHonor()
|
||||
honor.mergeFrom(honorBytes)
|
||||
info.level = honor.level.get()
|
||||
// 10315: medal_id not real group level
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err: Throwable) {
|
||||
LogCenter.log(err.stackTraceToString(), Level.WARN)
|
||||
}
|
||||
return if (info != null) {
|
||||
Result.success(info)
|
||||
} else {
|
||||
Result.failure(Exception("获取群成员信息失败"))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun requestGroupInfo(
|
||||
service: ITroopInfoService
|
||||
): Boolean {
|
||||
@ -217,12 +277,112 @@ internal object GroupHelper: QQInterfaces() {
|
||||
sendOidb("OidbSvc.0x55c_1", 1372, 1, array)
|
||||
}
|
||||
|
||||
suspend fun setGroupUniqueTitle(groupId: Long, userId: Long, title: String) {
|
||||
// ProfileService.Pb.ReqSystemMsgAction.Group
|
||||
suspend fun requestGroupRequest(
|
||||
msgSeq: Long,
|
||||
uin: Long,
|
||||
gid: Long,
|
||||
msg: String? = "",
|
||||
approve: Boolean? = true,
|
||||
notSee: Boolean? = false,
|
||||
subType: String
|
||||
): Result<String>{
|
||||
val req = structmsg.ReqSystemMsgAction().apply {
|
||||
if (subType == "invite") {
|
||||
msg_type.set(1)
|
||||
src_id.set(3)
|
||||
sub_src_id.set(10016)
|
||||
group_msg_type.set(2)
|
||||
} else {
|
||||
msg_type.set(2)
|
||||
src_id.set(2)
|
||||
sub_src_id.set(30024)
|
||||
group_msg_type.set(1)
|
||||
}
|
||||
msg_seq.set(msgSeq)
|
||||
req_uin.set(uin)
|
||||
sub_type.set(1)
|
||||
action_info.set(structmsg.SystemMsgActionInfo().apply {
|
||||
type.set(if (approve != false) 11 else 12)
|
||||
group_code.set(gid)
|
||||
if (subType == "add") {
|
||||
this.msg.set(msg)
|
||||
this.blacklist.set(notSee != false)
|
||||
}
|
||||
})
|
||||
language.set(1000)
|
||||
}
|
||||
val fromServiceMsg = sendBufferAW("ProfileService.Pb.ReqSystemMsgAction.Group", true, req.toByteArray())
|
||||
?: return Result.failure(Exception("ReqSystemMsgAction.Group: No Response"))
|
||||
if (fromServiceMsg.wupBuffer == null) {
|
||||
return Result.failure(Exception("ReqSystemMsgAction.Group: No WupBuffer"))
|
||||
}
|
||||
val rsp = structmsg.RspSystemMsgAction().mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
return if (rsp.head.result.has()) {
|
||||
if (rsp.head.result.get() == 0) {
|
||||
Result.success(rsp.msg_detail.get())
|
||||
} else {
|
||||
Result.failure(Exception(rsp.head.msg_fail.get()))
|
||||
}
|
||||
} else {
|
||||
Result.failure(Exception("操作失败"))
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun requestGroupSystemMsgNew(msgNum: Int, reqMsgType: Int = 1, latestFriendSeq: Long = 0, latestGroupSeq: Long = 0, retryCnt: Int = 5): List<structmsg.StructMsg> {
|
||||
if (retryCnt < 0) {
|
||||
return ArrayList()
|
||||
}
|
||||
val req = structmsg.ReqSystemMsgNew()
|
||||
req.msg_num.set(msgNum)
|
||||
req.latest_friend_seq.set(latestFriendSeq)
|
||||
req.latest_group_seq.set(latestGroupSeq)
|
||||
req.version.set(1000)
|
||||
req.checktype.set(3)
|
||||
val flag = structmsg.FlagInfo()
|
||||
flag.GrpMsg_Kick_Admin.set(1)
|
||||
flag.GrpMsg_HiddenGrp.set(1)
|
||||
flag.GrpMsg_WordingDown.set(1)
|
||||
// flag.FrdMsg_GetBusiCard.set(1)
|
||||
flag.GrpMsg_GetOfficialAccount.set(1)
|
||||
flag.GrpMsg_GetPayInGroup.set(1)
|
||||
flag.FrdMsg_Discuss2ManyChat.set(1)
|
||||
flag.GrpMsg_NotAllowJoinGrp_InviteNotFrd.set(1)
|
||||
flag.FrdMsg_NeedWaitingMsg.set(1)
|
||||
// flag.FrdMsg_uint32_need_all_unread_msg.set(1)
|
||||
flag.GrpMsg_NeedAutoAdminWording.set(1)
|
||||
flag.GrpMsg_get_transfer_group_msg_flag.set(1)
|
||||
flag.GrpMsg_get_quit_pay_group_msg_flag.set(1)
|
||||
flag.GrpMsg_support_invite_auto_join.set(1)
|
||||
flag.GrpMsg_mask_invite_auto_join.set(1)
|
||||
flag.GrpMsg_GetDisbandedByAdmin.set(1)
|
||||
flag.GrpMsg_GetC2cInviteJoinGroup.set(1)
|
||||
req.flag.set(flag)
|
||||
req.is_get_frd_ribbon.set(false)
|
||||
req.is_get_grp_ribbon.set(false)
|
||||
req.friend_msg_type_flag.set(1)
|
||||
req.uint32_req_msg_type.set(reqMsgType)
|
||||
req.uint32_need_uid.set(1)
|
||||
val fromServiceMsg = sendBufferAW("ProfileService.Pb.ReqSystemMsgNew.Group", true, req.toByteArray())
|
||||
return if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) {
|
||||
ArrayList()
|
||||
} else {
|
||||
try {
|
||||
val msg = structmsg.RspSystemMsgNew()
|
||||
msg.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
return msg.groupmsgs.get().orEmpty()
|
||||
} catch (err: Throwable) {
|
||||
requestGroupSystemMsgNew(msgNum, reqMsgType, latestFriendSeq, latestGroupSeq, retryCnt - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setGroupUniqueTitle(groupId: String, userId: String, title: String) {
|
||||
val localMemberInfo = getTroopMemberInfoByUin(groupId, userId, true).getOrThrow()
|
||||
val req = Oidb_0x8fc.ReqBody()
|
||||
req.uint64_group_code.set(groupId)
|
||||
req.uint64_group_code.set(groupId.toLong())
|
||||
val memberInfo = Oidb_0x8fc.MemberInfo()
|
||||
memberInfo.uint64_uin.set(userId)
|
||||
memberInfo.uint64_uin.set(userId.toLong())
|
||||
memberInfo.bytes_uin_name.set(ByteStringMicro.copyFromUtf8(localMemberInfo.troopnick.ifEmpty {
|
||||
localMemberInfo.troopremark.ifNullOrEmpty { "" }
|
||||
}))
|
||||
@ -241,22 +401,46 @@ internal object GroupHelper: QQInterfaces() {
|
||||
sendOidb("OidbSvc.0x89a_0", 2202, 0, reqBody.toByteArray())
|
||||
}
|
||||
|
||||
suspend fun getGroupMemberList(groupId: String, refresh: Boolean): Result<List<TroopMemberInfo>> {
|
||||
val service = app.getRuntimeService(ITroopMemberInfoService::class.java, "all")
|
||||
var memberList = service.getAllTroopMembers(groupId)
|
||||
if (refresh || memberList == null) {
|
||||
memberList = requestTroopMemberInfo(service, groupId).onFailure {
|
||||
return Result.failure(Exception("获取群成员列表失败"))
|
||||
}.getOrThrow()
|
||||
}
|
||||
|
||||
getGroupInfo(groupId, true).onSuccess {
|
||||
if(it.wMemberNum > memberList.size) {
|
||||
return getGroupMemberList(groupId, true)
|
||||
suspend fun getGroupMemberList(groupId: Long, refresh: Boolean): Result<HashMap<String, MemberInfo>> {
|
||||
val kernelService = NTServiceFetcher.kernelService
|
||||
val sessionService = kernelService.wrapperSession
|
||||
val service = sessionService.groupService
|
||||
val uids = suspendCancellableCoroutine { continuation ->
|
||||
service.getAllMemberList(groupId, refresh) { _, _, groupMemberListResult ->
|
||||
continuation.resume(groupMemberListResult?.ids?.map {
|
||||
it.uid
|
||||
})
|
||||
}
|
||||
}
|
||||
val memberMap = suspendCancellableCoroutine { continuation ->
|
||||
service.getMemberInfoForMqq(groupId, ArrayList(uids ?: emptyList()), refresh) { _, _, groupMemberListResult ->
|
||||
continuation.resume(groupMemberListResult.infos)
|
||||
}
|
||||
}
|
||||
// val extInfo = suspendCancellableCoroutine { continuation ->
|
||||
// service.getMemberExtInfo(GroupMemberExtReq().apply {
|
||||
// this.groupCode = groupId
|
||||
// this.beginUin = 0.toString()
|
||||
// this.groupType = ""
|
||||
// this.memberExtFilter = MemberExtInfoFilter().apply {
|
||||
// this.memberLevelInfoName = 1
|
||||
// this.memberLevelInfoUin = 1
|
||||
// this.nickName = 1
|
||||
// this.specialTitle = 1
|
||||
// this.memberLevelInfoActiveDay = 1
|
||||
// }
|
||||
// this.richCardNameVer = "1"
|
||||
// this.sourceType = 1
|
||||
// this.uinList = ArrayList(memberMap.values.toList().map {
|
||||
// it.uin
|
||||
// })
|
||||
// }) { _, _, groupMemberExtListResult ->
|
||||
// continuation.resume(groupMemberExtListResult)
|
||||
// }
|
||||
// }
|
||||
|
||||
return Result.success(memberMap)
|
||||
|
||||
return Result.success(memberList)
|
||||
}
|
||||
|
||||
suspend fun getProhibitedMemberList(groupId: Long): Result<List<ProhibitedMemberInfo>> {
|
||||
@ -269,11 +453,10 @@ internal object GroupHelper: QQInterfaces() {
|
||||
uint32_shutup_timestap.set(0)
|
||||
})
|
||||
}.toByteArray()) ?: return Result.failure(RuntimeException("[oidb] timeout"))
|
||||
if (!fromServiceMsg.isSuccess) {
|
||||
if (fromServiceMsg.wupBuffer == null) {
|
||||
return Result.failure(RuntimeException("[oidb] failed"))
|
||||
}
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
val body = fromServiceMsg.decodeToOidb()
|
||||
if(body.uint32_result.get() != 0) {
|
||||
return Result.failure(RuntimeException(body.str_error_msg.get()))
|
||||
}
|
||||
@ -291,11 +474,10 @@ internal object GroupHelper: QQInterfaces() {
|
||||
uint64_uin.set(app.longAccountUin)
|
||||
uint64_group_code.set(groupId)
|
||||
}.toByteArray(), trpc = true) ?: return Result.failure(RuntimeException("[oidb] timeout"))
|
||||
if (!fromServiceMsg.isSuccess) {
|
||||
if (fromServiceMsg.wupBuffer == null) {
|
||||
return Result.failure(RuntimeException("[oidb] failed"))
|
||||
}
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
val body = fromServiceMsg.decodeToOidb()
|
||||
if(body.uint32_result.get() != 0) {
|
||||
return Result.failure(RuntimeException(body.str_error_msg.get()))
|
||||
}
|
||||
@ -311,7 +493,7 @@ internal object GroupHelper: QQInterfaces() {
|
||||
toServiceMsg.extraData.putBoolean("is_admin", false)
|
||||
toServiceMsg.extraData.putInt("from", 0)
|
||||
val fromServiceMsg = sendToServiceMsgAW(toServiceMsg) ?: return@timeout Result.failure(Exception("获取群信息超时"))
|
||||
if (!fromServiceMsg.isSuccess) {
|
||||
if (fromServiceMsg.wupBuffer == null) {
|
||||
return@timeout Result.failure(Exception("获取群信息失败"))
|
||||
}
|
||||
val uniPacket = UniPacket(true)
|
||||
@ -367,13 +549,13 @@ internal object GroupHelper: QQInterfaces() {
|
||||
}
|
||||
|
||||
suspend fun getTroopMemberInfoByUin(
|
||||
groupId: Long,
|
||||
uin: Long,
|
||||
groupId: String,
|
||||
uin: String,
|
||||
refresh: Boolean = false
|
||||
): Result<TroopMemberInfo> {
|
||||
val service = app.getRuntimeService(ITroopMemberInfoService::class.java, "all")
|
||||
var info = service.getTroopMember(groupId.toString(), uin.toString())
|
||||
if (refresh || !service.isMemberInCache(groupId.toString(), uin.toString()) || info == null || info.troopnick == null) {
|
||||
var info = service.getTroopMember(groupId, uin)
|
||||
if (refresh || !service.isMemberInCache(groupId, uin) || info == null || info.troopnick == null) {
|
||||
info = requestTroopMemberInfo(service, groupId, uin).getOrNull()
|
||||
}
|
||||
if (info == null) {
|
||||
@ -387,13 +569,13 @@ internal object GroupHelper: QQInterfaces() {
|
||||
try {
|
||||
if (info != null && (info.alias == null || info.alias.isBlank())) {
|
||||
val req = group_member_info.ReqBody()
|
||||
req.uint64_group_code.set(groupId)
|
||||
req.uint64_uin.set(uin)
|
||||
req.uint64_group_code.set(groupId.toLong())
|
||||
req.uint64_uin.set(uin.toLong())
|
||||
req.bool_new_client.set(true)
|
||||
req.uint32_client_type.set(1)
|
||||
req.uint32_rich_card_name_ver.set(1)
|
||||
val fromServiceMsg = sendBufferAW("group_member_card.get_group_member_card_info", true, req.toByteArray())
|
||||
if (fromServiceMsg != null && fromServiceMsg.isSuccess) {
|
||||
if (fromServiceMsg != null && fromServiceMsg.wupBuffer != null) {
|
||||
val rsp = group_member_info.RspBody()
|
||||
rsp.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
if (rsp.msg_meminfo.str_location.has()) {
|
||||
@ -422,8 +604,8 @@ internal object GroupHelper: QQInterfaces() {
|
||||
}
|
||||
|
||||
suspend fun getTroopMemberInfoByUinViaNt(
|
||||
groupId: Long,
|
||||
qq: Long,
|
||||
groupId: String,
|
||||
qq: String,
|
||||
timeout: Long = 5000L
|
||||
): Result<MemberInfo> {
|
||||
return runCatching {
|
||||
@ -432,13 +614,13 @@ internal object GroupHelper: QQInterfaces() {
|
||||
val groupService = sessionService.groupService
|
||||
val info = withTimeoutOrNull(timeout) {
|
||||
suspendCancellableCoroutine {
|
||||
groupService.getTransferableMemberInfo(groupId) { code, _, data ->
|
||||
groupService.getTransferableMemberInfo(groupId.toLong()) { code, _, data ->
|
||||
if (code != 0) {
|
||||
it.resume(null)
|
||||
return@getTransferableMemberInfo
|
||||
}
|
||||
data.forEach { (_, info) ->
|
||||
if (info.uin == qq) {
|
||||
if (info.uin == qq.toLong()) {
|
||||
it.resume(info)
|
||||
return@forEach
|
||||
}
|
||||
@ -455,21 +637,21 @@ internal object GroupHelper: QQInterfaces() {
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun requestTroopMemberInfo(service: ITroopMemberInfoService, groupId: Long, memberUin: Long, timeout: Long = 10_000): Result<TroopMemberInfo> {
|
||||
private suspend fun requestTroopMemberInfo(service: ITroopMemberInfoService, groupId: String, memberUin: String, timeout: Long = 10_000): Result<TroopMemberInfo> {
|
||||
if(PlatformUtils.getQQVersionCode() >= QQ_9_0_65_VER) {
|
||||
return Result.failure(Exception("当前版本不支持该API"))
|
||||
}
|
||||
val info = RefreshTroopMemberInfoLock.withLock {
|
||||
val groupIdStr = groupId.toString()
|
||||
val memberUinStr = memberUin.toString()
|
||||
|
||||
service.deleteTroopMember(groupIdStr, memberUinStr)
|
||||
service.deleteTroopMember(groupId, memberUin)
|
||||
|
||||
requestMemberInfoV2(groupId, memberUin)
|
||||
requestMemberInfo(groupId, memberUin)
|
||||
|
||||
withTimeoutOrNull(timeout) {
|
||||
while (!service.isMemberInCache(groupIdStr, memberUinStr)) {
|
||||
while (!service.isMemberInCache(groupId, memberUin)) {
|
||||
delay(200)
|
||||
}
|
||||
return@withTimeoutOrNull service.getTroopMember(groupIdStr, memberUinStr)
|
||||
return@withTimeoutOrNull service.getTroopMember(groupId, memberUin)
|
||||
}
|
||||
}
|
||||
return if (info != null) {
|
||||
@ -479,7 +661,7 @@ internal object GroupHelper: QQInterfaces() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun requestMemberInfo(groupId: Long, memberUin: Long) {
|
||||
private fun requestMemberInfo(groupId: String, memberUin: String) {
|
||||
val businessHandler = app.getBusinessHandler(BusinessHandlerFactory.TROOP_MEMBER_CARD_HANDLER)
|
||||
|
||||
if (!::METHOD_REQ_MEMBER_INFO.isInitialized) {
|
||||
@ -491,10 +673,10 @@ internal object GroupHelper: QQInterfaces() {
|
||||
}
|
||||
}
|
||||
|
||||
METHOD_REQ_MEMBER_INFO.invoke(businessHandler, groupId, memberUin)
|
||||
METHOD_REQ_MEMBER_INFO.invoke(businessHandler, groupId.toLong(), memberUin.toLong())
|
||||
}
|
||||
|
||||
private fun requestMemberInfoV2(groupId: Long, memberUin: Long) {
|
||||
private fun requestMemberInfoV2(groupId: String, memberUin: String) {
|
||||
val businessHandler = app.getBusinessHandler(BusinessHandlerFactory.TROOP_MEMBER_CARD_HANDLER)
|
||||
|
||||
if (!::METHOD_REQ_MEMBER_INFO_V2.isInitialized) {
|
||||
@ -506,7 +688,8 @@ internal object GroupHelper: QQInterfaces() {
|
||||
}
|
||||
}
|
||||
|
||||
METHOD_REQ_MEMBER_INFO_V2.invoke(businessHandler, groupId.toString(), groupUin2GroupCode(groupId).toString(), arrayListOf(memberUin.toString()))
|
||||
METHOD_REQ_MEMBER_INFO_V2.invoke(businessHandler,
|
||||
groupId, groupUin2GroupCode(groupId.toLong()).toString(), arrayListOf(memberUin))
|
||||
}
|
||||
|
||||
private suspend fun requestTroopMemberInfo(service: ITroopMemberInfoService, groupId: String): Result<List<TroopMemberInfo>> {
|
||||
|
@ -1,49 +1,32 @@
|
||||
@file:OptIn(DelicateCoroutinesApi::class)
|
||||
|
||||
package qq.service.internals
|
||||
|
||||
import com.tencent.qqnt.kernel.nativeinterface.BroadcastHelperTransNotifyInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.Contact
|
||||
import com.tencent.qqnt.kernel.nativeinterface.ContactMsgBoxInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.CustomWithdrawConfig
|
||||
import com.tencent.qqnt.kernel.nativeinterface.DevInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.DownloadRelateEmojiResultInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.EmojiNotifyInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.EmojiResourceInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.FileTransNotifyInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.FirstViewDirectMsgNotifyInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.FirstViewGroupGuildInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.FreqLimitInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.GroupFileListResult
|
||||
import com.tencent.qqnt.kernel.nativeinterface.GroupGuildNotifyInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.GroupItem
|
||||
import com.tencent.qqnt.kernel.nativeinterface.GuildInteractiveNotificationItem
|
||||
import com.tencent.qqnt.kernel.nativeinterface.GuildMsgAbFlag
|
||||
import com.tencent.qqnt.kernel.nativeinterface.GuildNotificationAbstractInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.HitRelatedEmojiWordsResult
|
||||
import com.tencent.qqnt.kernel.nativeinterface.IKernelMsgListener
|
||||
import com.tencent.qqnt.kernel.nativeinterface.ImportOldDbMsgNotifyInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.InputStatusInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.KickedInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgAbstract
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgSetting
|
||||
import com.tencent.qqnt.kernel.nativeinterface.RecvdOrder
|
||||
import com.tencent.qqnt.kernel.nativeinterface.RelatedWordEmojiInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.SearchGroupFileResult
|
||||
import com.tencent.qqnt.kernel.nativeinterface.TabStatusInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.TempChatInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.UnreadCntInfo
|
||||
import com.tencent.qqnt.kernel.nativeinterface.TextElement
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.fuqiuluo.shamrock.config.AliveReply
|
||||
import moe.fuqiuluo.shamrock.config.get
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.helper.db.ImageDB
|
||||
import moe.fuqiuluo.shamrock.helper.db.ImageMapping
|
||||
import moe.fuqiuluo.shamrock.internals.GlobalEventTransmitter
|
||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils.QQ_9_0_8_VER
|
||||
import qq.service.bdh.RichProtoSvc
|
||||
import qq.service.file.GroupFileHelper
|
||||
import qq.service.group.GroupHelper
|
||||
import qq.service.kernel.SimpleKernelMsgListener
|
||||
import qq.service.msg.MessageHelper
|
||||
|
||||
object AioListener: IKernelMsgListener {
|
||||
override fun onRecvMsg(msgs: ArrayList<MsgRecord>) {
|
||||
msgs.forEach {
|
||||
object AioListener : SimpleKernelMsgListener() {
|
||||
override fun onRecvMsg(records: ArrayList<MsgRecord>) {
|
||||
records.forEach {
|
||||
GlobalScope.launch {
|
||||
try {
|
||||
onMsg(it)
|
||||
@ -54,9 +37,87 @@ object AioListener: IKernelMsgListener {
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun debugTest(record: MsgRecord, text: String) {
|
||||
if (record.chatType == MsgConstant.KCHATTYPEGROUP && text == ".shamrock.members") {
|
||||
val contact = MessageHelper.generateContact(record)
|
||||
GroupHelper.getGroupMemberList(record.peerUin, true).onSuccess {
|
||||
MessageHelper.sendMessage(contact, arrayListOf(
|
||||
MsgElement().apply {
|
||||
elementType = MsgConstant.KELEMTYPETEXT
|
||||
textElement = TextElement().apply {
|
||||
content = "memberCount: ${it.size}"
|
||||
}
|
||||
}
|
||||
), 3, MessageHelper.generateMsgId(record.chatType))
|
||||
}.onFailure {
|
||||
LogCenter.log("获取群成员列表失败: $it", Level.ERROR)
|
||||
}
|
||||
} else if (record.chatType == MsgConstant.KCHATTYPEGROUP && text == ".shamrock.root_files") {
|
||||
val contact = MessageHelper.generateContact(record)
|
||||
val files = GroupFileHelper.getGroupFiles(record.peerUin)
|
||||
MessageHelper.sendMessage(contact, arrayListOf(
|
||||
MsgElement().apply {
|
||||
elementType = MsgConstant.KELEMTYPETEXT
|
||||
textElement = TextElement().apply {
|
||||
content = "foldersCount: ${files.foldersCount}\nfilesCount: ${files.filesCount}"
|
||||
}
|
||||
}
|
||||
), 3, MessageHelper.generateMsgId(record.chatType))
|
||||
} else if (record.chatType == MsgConstant.KCHATTYPEGROUP && text == ".shamrock.pic_url") {
|
||||
val contact = MessageHelper.generateContact(record)
|
||||
val pic = record.elements.filter {
|
||||
it.elementType == MsgConstant.KELEMTYPEPIC
|
||||
}.map {
|
||||
val image = it.picElement
|
||||
val md5 = (image.md5HexStr ?: image.fileName
|
||||
.replace("{", "")
|
||||
.replace("}", "")
|
||||
.replace("-", "").split(".")[0])
|
||||
.uppercase()
|
||||
var storeId = 0
|
||||
if (PlatformUtils.getQQVersionCode() > QQ_9_0_8_VER) {
|
||||
storeId = image.storeID
|
||||
}
|
||||
val originalUrl = image.originImageUrl ?: ""
|
||||
return@map RichProtoSvc.getTempPicDownloadUrl(record.chatType, originalUrl, md5, image, storeId)
|
||||
}
|
||||
|
||||
MessageHelper.sendMessage(contact, arrayListOf(
|
||||
MsgElement().apply {
|
||||
elementType = MsgConstant.KELEMTYPETEXT
|
||||
textElement = TextElement().apply {
|
||||
content = "picUrl: \n${
|
||||
pic.joinToString("\n")
|
||||
}"
|
||||
}
|
||||
}
|
||||
), 3, MessageHelper.generateMsgId(record.chatType))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private suspend fun onMsg(record: MsgRecord) {
|
||||
if (AliveReply.get()) {
|
||||
val texts = record.elements.filter { it.elementType == MsgConstant.KELEMTYPETEXT }
|
||||
val text = texts.joinToString { it.textElement.content }
|
||||
if (texts.isNotEmpty() && text == "ping") {
|
||||
val contact = MessageHelper.generateContact(record)
|
||||
MessageHelper.sendMessage(contact, arrayListOf(
|
||||
MsgElement().apply {
|
||||
elementType = MsgConstant.KELEMTYPETEXT
|
||||
textElement = TextElement().apply {
|
||||
content = "pong"
|
||||
}
|
||||
}
|
||||
), 3, MessageHelper.generateMsgId(record.chatType))
|
||||
return
|
||||
}
|
||||
debugTest(record, text)
|
||||
}
|
||||
when (record.chatType) {
|
||||
MsgConstant.KCHATTYPEGROUP -> {
|
||||
if (record.senderUin == 0L) return
|
||||
|
||||
LogCenter.log("群消息(group = ${record.peerName}(${record.peerUin}), uin = ${record.senderUin}, id = ${record.msgId})")
|
||||
|
||||
if (!GlobalEventTransmitter.MessageTransmitter.transGroupMessage(record, record.elements)) {
|
||||
@ -85,7 +146,12 @@ object AioListener: IKernelMsgListener {
|
||||
|
||||
LogCenter.log("私聊临时消息(private = ${record.senderUin}, groupId=$groupCode)")
|
||||
|
||||
if (!GlobalEventTransmitter.MessageTransmitter.transTempMessage(record, record.elements, groupCode, fromNick)
|
||||
if (!GlobalEventTransmitter.MessageTransmitter.transTempMessage(
|
||||
record,
|
||||
record.elements,
|
||||
groupCode,
|
||||
fromNick
|
||||
)
|
||||
) {
|
||||
LogCenter.log("私聊临时消息推送失败 -> MessageTransmitter", Level.WARN)
|
||||
}
|
||||
@ -104,274 +170,66 @@ object AioListener: IKernelMsgListener {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMsgRecall(chatType: Int, peerId: String, msgId: Long) {
|
||||
LogCenter.log("onMsgRecall($chatType, $peerId, $msgId)")
|
||||
}
|
||||
|
||||
override fun onAddSendMsg(record: MsgRecord) {
|
||||
|
||||
}
|
||||
|
||||
override fun onMsgInfoListUpdate(msgList: ArrayList<MsgRecord>?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onTempChatInfoUpdate(tempChatInfo: TempChatInfo) {
|
||||
|
||||
}
|
||||
|
||||
override fun onMsgAbstractUpdate(arrayList: ArrayList<MsgAbstract>?) {
|
||||
//arrayList?.forEach {
|
||||
// LogCenter.log("onMsgAbstractUpdate($it)", Level.WARN)
|
||||
//}
|
||||
}
|
||||
|
||||
override fun onRecvMsgSvrRspTransInfo(
|
||||
j2: Long,
|
||||
contact: Contact?,
|
||||
i2: Int,
|
||||
i3: Int,
|
||||
str: String?,
|
||||
bArr: ByteArray?
|
||||
) {
|
||||
LogCenter.log("onRecvMsgSvrRspTransInfo($j2, $contact, $i2, $i3, $str)", Level.DEBUG)
|
||||
}
|
||||
|
||||
override fun onRecvS2CMsg(arrayList: ArrayList<Byte>?) {
|
||||
LogCenter.log("onRecvS2CMsg(${arrayList.toString()})", Level.DEBUG)
|
||||
}
|
||||
|
||||
override fun onRecvSysMsg(arrayList: ArrayList<Byte>?) {
|
||||
LogCenter.log("onRecvSysMsg(${arrayList.toString()})", Level.DEBUG)
|
||||
}
|
||||
|
||||
override fun onBroadcastHelperDownloadComplete(broadcastHelperTransNotifyInfo: BroadcastHelperTransNotifyInfo?) {}
|
||||
|
||||
override fun onBroadcastHelperProgerssUpdate(broadcastHelperTransNotifyInfo: BroadcastHelperTransNotifyInfo?) {}
|
||||
|
||||
override fun onChannelFreqLimitInfoUpdate(
|
||||
contact: Contact?,
|
||||
z: Boolean,
|
||||
freqLimitInfo: FreqLimitInfo?
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
override fun onContactUnreadCntUpdate(unreadMap: HashMap<Int, HashMap<String, UnreadCntInfo>>) {
|
||||
// 推送未读消息数量
|
||||
}
|
||||
|
||||
override fun onCustomWithdrawConfigUpdate(customWithdrawConfig: CustomWithdrawConfig?) {
|
||||
LogCenter.log("onCustomWithdrawConfigUpdate: " + customWithdrawConfig.toString(), Level.DEBUG)
|
||||
}
|
||||
|
||||
override fun onDraftUpdate(contact: Contact?, arrayList: ArrayList<MsgElement>?, j2: Long) {
|
||||
LogCenter.log("onDraftUpdate: " + contact.toString() + "|" + arrayList + "|" + j2.toString(), Level.DEBUG)
|
||||
}
|
||||
|
||||
override fun onEmojiDownloadComplete(emojiNotifyInfo: EmojiNotifyInfo?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onEmojiResourceUpdate(emojiResourceInfo: EmojiResourceInfo?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onFeedEventUpdate(firstViewDirectMsgNotifyInfo: FirstViewDirectMsgNotifyInfo?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onFileMsgCome(arrayList: ArrayList<MsgRecord>?) {
|
||||
|
||||
arrayList?.forEach { record ->
|
||||
GlobalScope.launch {
|
||||
when (record.chatType) {
|
||||
MsgConstant.KCHATTYPEGROUP -> onGroupFileMsg(record)
|
||||
MsgConstant.KCHATTYPEC2C -> onC2CFileMsg(record)
|
||||
else -> LogCenter.log("不支持该来源的文件上传事件:${record}", Level.WARN)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFirstViewDirectMsgUpdate(firstViewDirectMsgNotifyInfo: FirstViewDirectMsgNotifyInfo?) {
|
||||
private suspend fun onC2CFileMsg(record: MsgRecord) {
|
||||
val fileMsg = record.elements.firstOrNull {
|
||||
it.elementType == MsgConstant.KELEMTYPEFILE
|
||||
}?.fileElement ?: kotlin.run {
|
||||
LogCenter.log("消息为私聊文件消息但不包含文件消息,来自:${record.peerUin}", Level.WARN)
|
||||
return
|
||||
}
|
||||
|
||||
val fileName = fileMsg.fileName
|
||||
val fileSize = fileMsg.fileSize
|
||||
val expireTime = fileMsg.expireTime ?: 0
|
||||
val fileId = fileMsg.fileUuid
|
||||
val fileSubId = fileMsg.fileSubId ?: ""
|
||||
val url = RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId)
|
||||
|
||||
if (!GlobalEventTransmitter.FileNoticeTransmitter
|
||||
.transPrivateFileEvent(record.msgTime, record.senderUid, record.senderUin, fileId, fileSubId, fileName, fileSize, expireTime, url)
|
||||
) {
|
||||
LogCenter.log("私聊文件消息推送失败 -> FileNoticeTransmitter", Level.WARN)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFirstViewGroupGuildMapping(arrayList: ArrayList<FirstViewGroupGuildInfo>?) {
|
||||
private suspend fun onGroupFileMsg(record: MsgRecord) {
|
||||
val groupId = record.peerUin
|
||||
val fileMsg = record.elements.firstOrNull {
|
||||
it.elementType == MsgConstant.KELEMTYPEFILE
|
||||
}?.fileElement ?: kotlin.run {
|
||||
LogCenter.log("消息为群聊文件消息但不包含文件消息,来自:${record.peerUin}", Level.WARN)
|
||||
return
|
||||
}
|
||||
//val fileMd5 = fileMsg.fileMd5
|
||||
val fileName = fileMsg.fileName
|
||||
val fileSize = fileMsg.fileSize
|
||||
val uuid = fileMsg.fileUuid
|
||||
val bizId = fileMsg.fileBizId
|
||||
|
||||
val url = RichProtoSvc.getGroupFileDownUrl(record.peerUin, uuid, bizId)
|
||||
|
||||
if (!GlobalEventTransmitter.FileNoticeTransmitter
|
||||
.transGroupFileEvent(record.msgTime, record.senderUid, record.senderUin, groupId, uuid, fileName, fileSize, bizId, url)
|
||||
) {
|
||||
LogCenter.log("群聊文件消息推送失败 -> FileNoticeTransmitter", Level.WARN)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGrabPasswordRedBag(
|
||||
i2: Int,
|
||||
str: String?,
|
||||
i3: Int,
|
||||
recvdOrder: RecvdOrder?,
|
||||
msgRecord: MsgRecord?
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
override fun onKickedOffLine(kickedInfo: KickedInfo?) {
|
||||
LogCenter.log("onKickedOffLine($kickedInfo)")
|
||||
}
|
||||
|
||||
override fun onRichMediaUploadComplete(notifyInfo: FileTransNotifyInfo) {
|
||||
LogCenter.log({ "[BDH] 资源上传完成(${notifyInfo.trasferStatus}, ${notifyInfo.fileId}, ${notifyInfo.msgId}, ${notifyInfo.commonFileInfo})" }, Level.DEBUG)
|
||||
}
|
||||
|
||||
override fun onRecvOnlineFileMsg(arrayList: ArrayList<MsgRecord>?) {
|
||||
LogCenter.log(("onRecvOnlineFileMsg" + arrayList?.joinToString { ", " }), Level.DEBUG)
|
||||
}
|
||||
|
||||
override fun onRichMediaDownloadComplete(fileTransNotifyInfo: FileTransNotifyInfo) {
|
||||
|
||||
}
|
||||
|
||||
override fun onRichMediaProgerssUpdate(fileTransNotifyInfo: FileTransNotifyInfo) {
|
||||
|
||||
}
|
||||
|
||||
override fun onSearchGroupFileInfoUpdate(searchGroupFileResult: SearchGroupFileResult?) {
|
||||
LogCenter.log("onSearchGroupFileInfoUpdate($searchGroupFileResult)", Level.DEBUG)
|
||||
}
|
||||
|
||||
override fun onGroupFileInfoAdd(groupItem: GroupItem?) {
|
||||
LogCenter.log("onGroupFileInfoAdd: " + groupItem.toString(), Level.DEBUG)
|
||||
}
|
||||
|
||||
override fun onGroupFileInfoUpdate(groupFileListResult: GroupFileListResult?) {
|
||||
LogCenter.log("onGroupFileInfoUpdate: " + groupFileListResult.toString(), Level.DEBUG)
|
||||
}
|
||||
|
||||
override fun onGroupGuildUpdate(groupGuildNotifyInfo: GroupGuildNotifyInfo?) {
|
||||
LogCenter.log("onGroupGuildUpdate: " + groupGuildNotifyInfo.toString(), Level.DEBUG)
|
||||
}
|
||||
|
||||
override fun onGroupTransferInfoAdd(groupItem: GroupItem?) {
|
||||
LogCenter.log("onGroupTransferInfoAdd: " + groupItem.toString(), Level.DEBUG)
|
||||
}
|
||||
|
||||
override fun onGroupTransferInfoUpdate(groupFileListResult: GroupFileListResult?) {
|
||||
LogCenter.log("onGroupTransferInfoUpdate: " + groupFileListResult.toString(), Level.DEBUG)
|
||||
}
|
||||
|
||||
override fun onGuildInteractiveUpdate(guildInteractiveNotificationItem: GuildInteractiveNotificationItem?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onGuildMsgAbFlagChanged(guildMsgAbFlag: GuildMsgAbFlag?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onGuildNotificationAbstractUpdate(guildNotificationAbstractInfo: GuildNotificationAbstractInfo?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onHitCsRelatedEmojiResult(downloadRelateEmojiResultInfo: DownloadRelateEmojiResultInfo?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onHitEmojiKeywordResult(hitRelatedEmojiWordsResult: HitRelatedEmojiWordsResult?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onHitRelatedEmojiResult(relatedWordEmojiInfo: RelatedWordEmojiInfo?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onImportOldDbProgressUpdate(importOldDbMsgNotifyInfo: ImportOldDbMsgNotifyInfo?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onInputStatusPush(inputStatusInfo: InputStatusInfo?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onLineDev(devList: ArrayList<DevInfo>?) {
|
||||
//LogCenter.log("onLineDev($arrayList)")
|
||||
}
|
||||
|
||||
override fun onLogLevelChanged(newLevel: Long) {
|
||||
|
||||
}
|
||||
|
||||
override fun onMsgBoxChanged(arrayList: ArrayList<ContactMsgBoxInfo>?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onMsgDelete(contact: Contact?, arrayList: ArrayList<Long>?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onMsgEventListUpdate(hashMap: HashMap<String, ArrayList<Long>>?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onMsgInfoListAdd(arrayList: ArrayList<MsgRecord>?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onMsgQRCodeStatusChanged(i2: Int) {
|
||||
|
||||
}
|
||||
|
||||
override fun onMsgSecurityNotify(msgRecord: MsgRecord?) {
|
||||
LogCenter.log("onMsgSecurityNotify($msgRecord)")
|
||||
}
|
||||
|
||||
override fun onMsgSettingUpdate(msgSetting: MsgSetting?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onNtFirstViewMsgSyncEnd() {
|
||||
|
||||
}
|
||||
|
||||
override fun onNtMsgSyncEnd() {
|
||||
LogCenter.log("NTKernel同步消息完成", Level.DEBUG)
|
||||
}
|
||||
|
||||
override fun onNtMsgSyncStart() {
|
||||
LogCenter.log("NTKernel同步消息开始", Level.DEBUG)
|
||||
}
|
||||
|
||||
override fun onReadFeedEventUpdate(firstViewDirectMsgNotifyInfo: FirstViewDirectMsgNotifyInfo?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onRecvGroupGuildFlag(i2: Int) {
|
||||
|
||||
}
|
||||
|
||||
override fun onRecvUDCFlag(i2: Int) {
|
||||
LogCenter.log("onRecvUDCFlag($i2)", Level.DEBUG)
|
||||
}
|
||||
|
||||
override fun onSendMsgError(j2: Long, contact: Contact?, i2: Int, str: String?) {
|
||||
LogCenter.log("onSendMsgError($j2, $contact, $j2, $str)", Level.DEBUG)
|
||||
}
|
||||
|
||||
override fun onSysMsgNotification(i2: Int, j2: Long, j3: Long, arrayList: ArrayList<Byte>?) {
|
||||
LogCenter.log("onSysMsgNotification($i2, $j2, $j3, $arrayList)", Level.DEBUG)
|
||||
}
|
||||
|
||||
override fun onUnreadCntAfterFirstView(hashMap: HashMap<Int, ArrayList<UnreadCntInfo>>?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onUnreadCntUpdate(hashMap: HashMap<Int, ArrayList<UnreadCntInfo>>?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onUserChannelTabStatusChanged(z: Boolean) {
|
||||
|
||||
}
|
||||
|
||||
override fun onUserOnlineStatusChanged(z: Boolean) {
|
||||
|
||||
}
|
||||
|
||||
override fun onUserTabStatusChanged(arrayList: ArrayList<TabStatusInfo>?) {
|
||||
LogCenter.log("onUserTabStatusChanged($arrayList)", Level.DEBUG)
|
||||
}
|
||||
|
||||
override fun onlineStatusBigIconDownloadPush(i2: Int, j2: Long, str: String?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onlineStatusSmallIconDownloadPush(i2: Int, j2: Long, str: String?) {
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
override fun onRecvSysMsg(arrayList: ArrayList<Byte>?) {
|
||||
LogCenter.log("onRecvSysMsg")
|
||||
LogCenter.log(arrayList?.toByteArray()?.toHexString() ?: "")
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user