mirror of
https://github.com/whitechi73/OpenShamrock.git
synced 2024-08-14 05:12:17 +00:00
Compare commits
2 Commits
7bacea3288
...
3a07116093
Author | SHA1 | Date | |
---|---|---|---|
3a07116093 | |||
a95d8d85e8 |
@ -37,7 +37,6 @@ add_library(${CMAKE_PROJECT_NAME} SHARED
|
|||||||
# List C/C++ source files with relative paths to this CMakeLists.txt.
|
# List C/C++ source files with relative paths to this CMakeLists.txt.
|
||||||
${SRC_DIR}
|
${SRC_DIR}
|
||||||
md5.cpp
|
md5.cpp
|
||||||
cqcode.cpp
|
|
||||||
silk.cpp
|
silk.cpp
|
||||||
message.cpp
|
message.cpp
|
||||||
shamrock.cpp)
|
shamrock.cpp)
|
||||||
|
@ -1,138 +0,0 @@
|
|||||||
#include <stdexcept>
|
|
||||||
#include "cqcode.h"
|
|
||||||
|
|
||||||
inline void replace_string(std::string& str, const std::string& from, const std::string& to) {
|
|
||||||
size_t startPos = 0;
|
|
||||||
while ((startPos = str.find(from, startPos)) != std::string::npos) {
|
|
||||||
str.replace(startPos, from.length(), to);
|
|
||||||
startPos += to.length();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline int utf8_next_len(const std::string& str, size_t offset)
|
|
||||||
{
|
|
||||||
uint8_t c = (uint8_t)str[offset];
|
|
||||||
if (c >= 0xFC)
|
|
||||||
return 6;
|
|
||||||
else if (c >= 0xF8)
|
|
||||||
return 5;
|
|
||||||
else if (c >= 0xF0)
|
|
||||||
return 4;
|
|
||||||
else if (c >= 0xE0)
|
|
||||||
return 3;
|
|
||||||
else if (c >= 0xC0)
|
|
||||||
return 2;
|
|
||||||
else if (c > 0x00)
|
|
||||||
return 1;
|
|
||||||
else
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void decode_cqcode(const std::string& code, std::vector<std::unordered_map<std::string, std::string>>& dest) {
|
|
||||||
std::string cache;
|
|
||||||
bool is_start = false;
|
|
||||||
std::string key_tmp;
|
|
||||||
std::unordered_map<std::string, std::string> kv;
|
|
||||||
for(size_t i = 0; i < code.size(); i++) {
|
|
||||||
int utf8_char_len = utf8_next_len(code, i);
|
|
||||||
if(utf8_char_len == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
std::string_view c(&code[i],utf8_char_len);
|
|
||||||
if (c == "[") {
|
|
||||||
if (is_start) {
|
|
||||||
throw illegal_code();
|
|
||||||
} else {
|
|
||||||
if (!cache.empty()) {
|
|
||||||
std::unordered_map<std::string, std::string> kv;
|
|
||||||
replace_string(cache, "[", "[");
|
|
||||||
replace_string(cache, "]", "]");
|
|
||||||
replace_string(cache, "&", "&");
|
|
||||||
kv.emplace("_type", "text");
|
|
||||||
kv.emplace("text", cache);
|
|
||||||
dest.push_back(kv);
|
|
||||||
cache.clear();
|
|
||||||
}
|
|
||||||
std::string_view cq_flag(&code[i],4);
|
|
||||||
if(cq_flag == "[CQ:"){
|
|
||||||
is_start = true;
|
|
||||||
i += 3;
|
|
||||||
}else{
|
|
||||||
cache += c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (c == "=") {
|
|
||||||
if (is_start) {
|
|
||||||
if (cache.empty()) {
|
|
||||||
throw illegal_code();
|
|
||||||
} else {
|
|
||||||
if (key_tmp.empty()) {
|
|
||||||
key_tmp.append(cache);
|
|
||||||
cache.clear();
|
|
||||||
} else {
|
|
||||||
cache += c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cache += c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (c == ",") {
|
|
||||||
if (is_start) {
|
|
||||||
if (kv.count("_type") == 0 && !cache.empty()) {
|
|
||||||
kv.emplace("_type", cache);
|
|
||||||
cache.clear();
|
|
||||||
} else {
|
|
||||||
if (!key_tmp.empty()) {
|
|
||||||
replace_string(cache, "[", "[");
|
|
||||||
replace_string(cache, "]", "]");
|
|
||||||
replace_string(cache, ",", ",");
|
|
||||||
replace_string(cache, "&", "&");
|
|
||||||
kv.emplace(key_tmp, cache);
|
|
||||||
cache.clear();
|
|
||||||
key_tmp.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cache += c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (c == "]") {
|
|
||||||
if (is_start) {
|
|
||||||
if (!cache.empty()) {
|
|
||||||
if (!key_tmp.empty()) {
|
|
||||||
replace_string(cache, "[", "[");
|
|
||||||
replace_string(cache, "]", "]");
|
|
||||||
replace_string(cache, ",", ",");
|
|
||||||
replace_string(cache, "&", "&");
|
|
||||||
kv.emplace(key_tmp, cache);
|
|
||||||
} else {
|
|
||||||
kv.emplace("_type", cache);
|
|
||||||
}
|
|
||||||
dest.push_back(kv);
|
|
||||||
kv.clear();
|
|
||||||
key_tmp.clear();
|
|
||||||
cache.clear();
|
|
||||||
is_start = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cache += c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
cache += c;
|
|
||||||
i += (utf8_char_len - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!cache.empty()) {
|
|
||||||
std::unordered_map<std::string, std::string> kv;
|
|
||||||
replace_string(cache, "[", "[");
|
|
||||||
replace_string(cache, "]", "]");
|
|
||||||
replace_string(cache, "&", "&");
|
|
||||||
kv.emplace("_type", "text");
|
|
||||||
kv.emplace("text", cache);
|
|
||||||
dest.push_back(kv);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,87 +0,0 @@
|
|||||||
#include "jni.h"
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
struct Honor {
|
|
||||||
int id;
|
|
||||||
std::string name;
|
|
||||||
std::string icon_url;
|
|
||||||
int priority;
|
|
||||||
};
|
|
||||||
|
|
||||||
int calc_honor_flag(int honor_id, char honor_flag);
|
|
||||||
|
|
||||||
jobject make_honor_object(JNIEnv *env, jobject user_id, const Honor& honor);
|
|
||||||
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT jobject JNICALL
|
|
||||||
Java_moe_fuqiuluo_shamrock_remote_action_handlers_GetTroopHonor_nativeDecodeHonor(JNIEnv *env, jobject thiz,
|
|
||||||
jstring user_id,
|
|
||||||
jint honor_id,
|
|
||||||
jbyte honor_flag) {
|
|
||||||
static std::vector<Honor> honor_list = {
|
|
||||||
Honor{1, "龙王", "https://qzonestyle.gtimg.cn/aoi/sola/20200213150116_n4PxCiurbm.png", 1},
|
|
||||||
Honor{2, "群聊之火", "https://qzonestyle.gtimg.cn/aoi/sola/20200217190136_92JEGFKC5k.png", 3},
|
|
||||||
Honor{3, "群聊炽焰", "https://qzonestyle.gtimg.cn/aoi/sola/20200217190204_zgCTeSrMq1.png", 4},
|
|
||||||
Honor{5, "冒尖小春笋", "https://qzonestyle.gtimg.cn/aoi/sola/20200213150335_tUJCAtoKVP.png", 5},
|
|
||||||
Honor{6, "快乐源泉", "https://qzonestyle.gtimg.cn/aoi/sola/20200213150434_3tDmsJExCP.png", 7},
|
|
||||||
Honor{7, "学术新星", "https://sola.gtimg.cn/aoi/sola/20200515140645_j0X6gbuHNP.png", 8},
|
|
||||||
Honor{8, "顶尖学霸", "https://sola.gtimg.cn/aoi/sola/20200515140639_0CtWOpfVzK.png", 9},
|
|
||||||
Honor{9, "至尊学神", "https://sola.gtimg.cn/aoi/sola/20200515140628_P8UEYBjMBT.png", 10},
|
|
||||||
Honor{10, "一笔当先", "https://sola.gtimg.cn/aoi/sola/20200515140654_4r94tSCdaB.png", 11},
|
|
||||||
Honor{11, "奋进小翠竹", "https://sola.gtimg.cn/aoi/sola/20200812151819_wbj6z2NGoB.png", 6},
|
|
||||||
Honor{12, "氛围魔杖", "https://sola.gtimg.cn/aoi/sola/20200812151831_4ZJgQCaD1H.png", 2},
|
|
||||||
Honor{13, "壕礼皇冠", "https://sola.gtimg.cn/aoi/sola/20200930154050_juZOAMg7pt.png", 12},
|
|
||||||
};
|
|
||||||
int flag = calc_honor_flag(honor_id, honor_flag);
|
|
||||||
if ((honor_id != 1 && honor_id != 2 && honor_id != 3) || flag != 1) {
|
|
||||||
auto honor = *std::find_if(honor_list.begin(), honor_list.end(), [&honor_id](auto &honor) {
|
|
||||||
return honor.id == honor_id;
|
|
||||||
});
|
|
||||||
return make_honor_object(env, user_id, honor);
|
|
||||||
} else {
|
|
||||||
auto honor = *std::find_if(honor_list.begin(), honor_list.end(), [&honor_id](auto &honor) {
|
|
||||||
return honor.id == honor_id;
|
|
||||||
});
|
|
||||||
std::string url = "https://static-res.qq.com/static-res/groupInteract/vas/a/" + std::to_string(honor_id) + "_1.png";
|
|
||||||
honor = Honor{honor_id, honor.name, url, honor.priority};
|
|
||||||
return make_honor_object(env, user_id, honor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int calc_honor_flag(int honor_id, char honor_flag) {
|
|
||||||
int flag;
|
|
||||||
if (honor_flag == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (honor_id == 1) {
|
|
||||||
flag = honor_flag;
|
|
||||||
} else if (honor_id == 2 || honor_id == 3) {
|
|
||||||
flag = honor_flag >> 2;
|
|
||||||
} else if (honor_id != 4) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
flag = honor_flag >> 4;
|
|
||||||
}
|
|
||||||
return flag & 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
jobject make_honor_object(JNIEnv *env, jobject user_id, const Honor& honor) {
|
|
||||||
jclass GroupMemberHonor = env->FindClass("moe/fuqiuluo/shamrock/remote/service/data/GroupMemberHonor");
|
|
||||||
jmethodID GroupMemberHonor_init = env->GetMethodID(GroupMemberHonor, "<init>",
|
|
||||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILjava/lang/String;)V");
|
|
||||||
auto user_id_str = (jstring) user_id;
|
|
||||||
jstring honor_desc = env->NewStringUTF(honor.name.c_str());
|
|
||||||
jstring uin_name = env->NewStringUTF("");
|
|
||||||
jstring honor_icon_url = env->NewStringUTF(honor.icon_url.c_str());
|
|
||||||
jobject ret = env->NewObject(GroupMemberHonor, GroupMemberHonor_init, user_id_str, uin_name, honor_icon_url, 0, honor.id, honor_desc);
|
|
||||||
|
|
||||||
env->DeleteLocalRef(GroupMemberHonor);
|
|
||||||
env->DeleteLocalRef(user_id_str);
|
|
||||||
env->DeleteLocalRef(honor_desc);
|
|
||||||
env->DeleteLocalRef(honor_icon_url);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
#ifndef UNTITLED_CQCODE_H
|
|
||||||
#define UNTITLED_CQCODE_H
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
#include <exception>
|
|
||||||
|
|
||||||
class illegal_code: std::exception {
|
|
||||||
public:
|
|
||||||
[[nodiscard]] const char * what() const noexcept override {
|
|
||||||
return "Error cq code.";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void decode_cqcode(const std::string& code, std::vector<std::unordered_map<std::string, std::string>>& dest);
|
|
||||||
|
|
||||||
void encode_cqcode(const std::vector<std::unordered_map<std::string, std::string>>& segment, std::string& dest);
|
|
||||||
|
|
||||||
#endif //UNTITLED_CQCODE_H
|
|
@ -1,5 +1,4 @@
|
|||||||
#include "jni.h"
|
#include "jni.h"
|
||||||
#include "cqcode.h"
|
|
||||||
#include <random>
|
#include <random>
|
||||||
|
|
||||||
inline void replace_string(std::string& str, const std::string& from, const std::string& to) {
|
inline void replace_string(std::string& str, const std::string& from, const std::string& to) {
|
||||||
@ -12,7 +11,7 @@ inline void replace_string(std::string& str, const std::string& from, const std:
|
|||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jlong JNICALL
|
JNIEXPORT jlong JNICALL
|
||||||
Java_moe_fuqiuluo_shamrock_helper_MessageHelper_createMessageUniseq(JNIEnv *env, jobject thiz,
|
Java_qq_service_msg_MessageHelper_createMessageUniseq(JNIEnv *env, jobject thiz,
|
||||||
jint chat_type,
|
jint chat_type,
|
||||||
jlong time) {
|
jlong time) {
|
||||||
static std::random_device rd;
|
static std::random_device rd;
|
||||||
@ -32,123 +31,6 @@ Java_moe_fuqiuluo_shamrock_helper_MessageHelper_getChatType(JNIEnv *env, jobject
|
|||||||
return (int32_t) ((int64_t) msg_id & 0xffL);
|
return (int32_t) ((int64_t) msg_id & 0xffL);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT jobject JNICALL
|
|
||||||
Java_moe_fuqiuluo_shamrock_helper_MessageHelper_nativeDecodeCQCode(JNIEnv *env, jobject thiz,
|
|
||||||
jstring code) {
|
|
||||||
jclass ArrayList = env->FindClass("java/util/ArrayList");
|
|
||||||
jmethodID NewArrayList = env->GetMethodID(ArrayList, "<init>", "()V");
|
|
||||||
jmethodID ArrayListAdd = env->GetMethodID(ArrayList, "add", "(Ljava/lang/Object;)Z");
|
|
||||||
jobject arrayList = env->NewObject(ArrayList, NewArrayList);
|
|
||||||
|
|
||||||
const char* cCode = env->GetStringUTFChars(code, nullptr);
|
|
||||||
std::string cppCode = cCode;
|
|
||||||
std::vector<std::unordered_map<std::string, std::string>> dest;
|
|
||||||
try {
|
|
||||||
decode_cqcode(cppCode, dest);
|
|
||||||
} catch (illegal_code& code) {
|
|
||||||
return arrayList;
|
|
||||||
}
|
|
||||||
|
|
||||||
jclass HashMap = env->FindClass("java/util/HashMap");
|
|
||||||
jmethodID NewHashMap = env->GetMethodID(HashMap, "<init>", "()V");
|
|
||||||
jclass String = env->FindClass("java/lang/String");
|
|
||||||
jmethodID NewString = env->GetMethodID(String, "<init>", "([BLjava/lang/String;)V");
|
|
||||||
jstring charset = env->NewStringUTF("UTF-8");
|
|
||||||
jmethodID put = env->GetMethodID(HashMap, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
|
|
||||||
for (auto& map : dest) {
|
|
||||||
jobject hashMap = env->NewObject(HashMap, NewHashMap);
|
|
||||||
for (const auto& pair : map) {
|
|
||||||
jbyteArray keyArray = env->NewByteArray((int) pair.first.size());
|
|
||||||
jbyteArray valueArray = env->NewByteArray((int) pair.second.size());
|
|
||||||
env->SetByteArrayRegion(keyArray, 0, (int) pair.first.size(), (jbyte*)pair.first.c_str());
|
|
||||||
env->SetByteArrayRegion(valueArray, 0, (int) pair.second.size(), (jbyte*)pair.second.c_str());
|
|
||||||
auto key = (jstring) env->NewObject(String, NewString, keyArray, charset);
|
|
||||||
auto value = (jstring) env->NewObject(String, NewString, valueArray, charset);
|
|
||||||
env->CallObjectMethod(hashMap, put, key, value);
|
|
||||||
}
|
|
||||||
env->CallBooleanMethod(arrayList, ArrayListAdd, hashMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
env->DeleteLocalRef(ArrayList);
|
|
||||||
env->DeleteLocalRef(HashMap);
|
|
||||||
env->DeleteLocalRef(String);
|
|
||||||
env->DeleteLocalRef(charset);
|
|
||||||
env->ReleaseStringUTFChars(code, cCode);
|
|
||||||
|
|
||||||
return arrayList;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT jstring JNICALL
|
|
||||||
Java_moe_fuqiuluo_shamrock_helper_MessageHelper_nativeEncodeCQCode(JNIEnv *env, jobject thiz,
|
|
||||||
jobject segment_list) {
|
|
||||||
jclass List = env->FindClass("java/util/List");
|
|
||||||
jmethodID ListSize = env->GetMethodID(List, "size", "()I");
|
|
||||||
jmethodID ListGet = env->GetMethodID(List, "get", "(I)Ljava/lang/Object;");
|
|
||||||
jclass Map = env->FindClass("java/util/Map");
|
|
||||||
jmethodID MapGet = env->GetMethodID(Map, "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
|
|
||||||
jmethodID entrySetMethod = env->GetMethodID(Map, "entrySet", "()Ljava/util/Set;");
|
|
||||||
jclass setClass = env->FindClass("java/util/Set");
|
|
||||||
jmethodID iteratorMethod = env->GetMethodID(setClass, "iterator", "()Ljava/util/Iterator;");
|
|
||||||
jclass entryClass = env->FindClass("java/util/Map$Entry");
|
|
||||||
jmethodID getKeyMethod = env->GetMethodID(entryClass, "getKey", "()Ljava/lang/Object;");
|
|
||||||
jmethodID getValueMethod = env->GetMethodID(entryClass, "getValue", "()Ljava/lang/Object;");
|
|
||||||
|
|
||||||
std::string result;
|
|
||||||
jint size = env->CallIntMethod(segment_list, ListSize);
|
|
||||||
for (int i = 0; i < size; i++ ) {
|
|
||||||
jobject segment = env->CallObjectMethod(segment_list, ListGet, i);
|
|
||||||
jobject entrySet = env->CallObjectMethod(segment, entrySetMethod);
|
|
||||||
jobject iterator = env->CallObjectMethod(entrySet, iteratorMethod);
|
|
||||||
auto type = (jstring) env->CallObjectMethod(segment, MapGet, env->NewStringUTF("_type"));
|
|
||||||
auto typeString = env->GetStringUTFChars(type, nullptr);
|
|
||||||
if (strcmp(typeString, "text") == 0) {
|
|
||||||
auto text = (jstring) env->CallObjectMethod(segment, MapGet, env->NewStringUTF("text"));
|
|
||||||
auto textString = env->GetStringUTFChars(text, nullptr);
|
|
||||||
std::string tmpValue = textString;
|
|
||||||
replace_string(tmpValue, "&", "&");
|
|
||||||
replace_string(tmpValue, "[", "[");
|
|
||||||
replace_string(tmpValue, "]", "]");
|
|
||||||
replace_string(tmpValue, ",", ",");
|
|
||||||
result.append(tmpValue);
|
|
||||||
env->ReleaseStringUTFChars(text, textString);
|
|
||||||
} else {
|
|
||||||
result.append("[CQ:");
|
|
||||||
result.append(typeString);
|
|
||||||
while (env->CallBooleanMethod(iterator, env->GetMethodID(env->GetObjectClass(iterator), "hasNext", "()Z"))) {
|
|
||||||
jobject entry = env->CallObjectMethod(iterator, env->GetMethodID(env->GetObjectClass(iterator), "next", "()Ljava/lang/Object;"));
|
|
||||||
auto key = (jstring) env->CallObjectMethod(entry, getKeyMethod);
|
|
||||||
auto value = (jstring) env->CallObjectMethod(entry, getValueMethod);
|
|
||||||
auto keyString = env->GetStringUTFChars(key, nullptr);
|
|
||||||
auto valueString = env->GetStringUTFChars(value, nullptr);
|
|
||||||
if (strcmp(keyString, "_type") != 0) {
|
|
||||||
std::string tmpValue = valueString;
|
|
||||||
replace_string(tmpValue, "&", "&");
|
|
||||||
replace_string(tmpValue, "[", "[");
|
|
||||||
replace_string(tmpValue, "]", "]");
|
|
||||||
replace_string(tmpValue, ",", ",");
|
|
||||||
result.append(",").append(keyString).append("=").append(tmpValue);
|
|
||||||
}
|
|
||||||
env->ReleaseStringUTFChars(key, keyString);
|
|
||||||
env->ReleaseStringUTFChars(value, valueString);
|
|
||||||
env->DeleteLocalRef(entry);
|
|
||||||
env->DeleteLocalRef(key);
|
|
||||||
env->DeleteLocalRef(value);
|
|
||||||
}
|
|
||||||
result.append("]");
|
|
||||||
}
|
|
||||||
env->ReleaseStringUTFChars(type, typeString);
|
|
||||||
}
|
|
||||||
|
|
||||||
env->DeleteLocalRef(List);
|
|
||||||
env->DeleteLocalRef(Map);
|
|
||||||
env->DeleteLocalRef(setClass);
|
|
||||||
env->DeleteLocalRef(entryClass);
|
|
||||||
return env->NewStringUTF(result.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jlong JNICALL
|
JNIEXPORT jlong JNICALL
|
||||||
Java_moe_fuqiuluo_shamrock_helper_MessageHelper_insertChatTypeToMsgId(JNIEnv *env, jobject thiz,
|
Java_moe_fuqiuluo_shamrock_helper_MessageHelper_insertChatTypeToMsgId(JNIEnv *env, jobject thiz,
|
||||||
|
@ -9,7 +9,7 @@ import moe.fuqiuluo.symbols.Protobuf
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class NtV2RichMediaRsp(
|
data class NtV2RichMediaRsp(
|
||||||
@ProtoNumber(1) val head: RspHead,
|
@ProtoNumber(1) val head: RspHead?,
|
||||||
@ProtoNumber(2) val upload: UploadRsp?,
|
@ProtoNumber(2) val upload: UploadRsp?,
|
||||||
@ProtoNumber(3) val download: DownloadRsp?,
|
@ProtoNumber(3) val download: DownloadRsp?,
|
||||||
@ProtoNumber(4) val downloadRkeyRsp: DownloadRkeyRsp?,
|
@ProtoNumber(4) val downloadRkeyRsp: DownloadRkeyRsp?,
|
||||||
|
@ -18,6 +18,11 @@ public class MsgService {
|
|||||||
public void addMsgListener(IKernelMsgListener listener) {
|
public void addMsgListener(IKernelMsgListener listener) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeMsgListener(@NotNull IKernelMsgListener iKernelMsgListener) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public String getRichMediaFilePathForGuild(@NotNull RichMediaFilePathInfo richMediaFilePathInfo) {
|
public String getRichMediaFilePathForGuild(@NotNull RichMediaFilePathInfo richMediaFilePathInfo) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,41 @@
|
|||||||
package kritor.service
|
package kritor.service
|
||||||
|
|
||||||
|
import io.grpc.Status
|
||||||
|
import io.grpc.StatusRuntimeException
|
||||||
import io.kritor.event.EventRequest
|
import io.kritor.event.EventRequest
|
||||||
import io.kritor.event.EventServiceGrpcKt
|
import io.kritor.event.EventServiceGrpcKt
|
||||||
import io.kritor.event.EventStructure
|
import io.kritor.event.EventStructure
|
||||||
import io.kritor.event.EventType
|
import io.kritor.event.EventType
|
||||||
|
import io.kritor.event.RequestPushEvent
|
||||||
import io.kritor.event.eventStructure
|
import io.kritor.event.eventStructure
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.channelFlow
|
import kotlinx.coroutines.flow.channelFlow
|
||||||
import moe.fuqiuluo.shamrock.internals.GlobalEventTransmitter
|
import moe.fuqiuluo.shamrock.internals.GlobalEventTransmitter
|
||||||
|
|
||||||
object EventService: EventServiceGrpcKt.EventServiceCoroutineImplBase() {
|
object EventService: EventServiceGrpcKt.EventServiceCoroutineImplBase() {
|
||||||
override fun registerActiveListener(request: EventRequest): Flow<EventStructure> {
|
override fun registerActiveListener(request: RequestPushEvent): Flow<EventStructure> {
|
||||||
return channelFlow {
|
return channelFlow {
|
||||||
when(request.type!!) {
|
when(request.type!!) {
|
||||||
EventType.CORE_EVENT -> TODO()
|
EventType.EVENT_TYPE_CORE_EVENT -> {}
|
||||||
EventType.MESSAGE -> GlobalEventTransmitter.onMessageEvent {
|
EventType.EVENT_TYPE_MESSAGE -> GlobalEventTransmitter.onMessageEvent {
|
||||||
send(eventStructure {
|
send(eventStructure {
|
||||||
this.type = EventType.MESSAGE
|
this.type = EventType.EVENT_TYPE_MESSAGE
|
||||||
this.message = it.second
|
this.message = it.second
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
EventType.NOTICE -> TODO()
|
EventType.EVENT_TYPE_NOTICE -> GlobalEventTransmitter.onRequestEvent {
|
||||||
EventType.REQUEST -> TODO()
|
send(eventStructure {
|
||||||
EventType.UNRECOGNIZED -> TODO()
|
this.type = EventType.EVENT_TYPE_NOTICE
|
||||||
|
this.request = it
|
||||||
|
})
|
||||||
|
}
|
||||||
|
EventType.EVENT_TYPE_REQUEST -> GlobalEventTransmitter.onNoticeEvent {
|
||||||
|
send(eventStructure {
|
||||||
|
this.type = EventType.EVENT_TYPE_NOTICE
|
||||||
|
this.notice = it
|
||||||
|
})
|
||||||
|
}
|
||||||
|
EventType.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCorouti
|
|||||||
).toByteArray()
|
).toByteArray()
|
||||||
val fromServiceMsg = QQInterfaces.sendOidbAW("OidbSvc.0x6d7_0", 1751, 0, data)
|
val fromServiceMsg = QQInterfaces.sendOidbAW("OidbSvc.0x6d7_0", 1751, 0, data)
|
||||||
?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
|
?: 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"))
|
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
||||||
}
|
}
|
||||||
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
||||||
@ -57,7 +57,7 @@ internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCorouti
|
|||||||
folderId = request.folderId
|
folderId = request.folderId
|
||||||
)
|
)
|
||||||
).toByteArray()) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
|
).toByteArray()) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
|
||||||
if (!fromServiceMsg.isSuccess) {
|
if (fromServiceMsg.wupBuffer == null) {
|
||||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
||||||
}
|
}
|
||||||
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
||||||
@ -82,7 +82,7 @@ internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCorouti
|
|||||||
}
|
}
|
||||||
val fromServiceMsg = QQInterfaces.sendOidbAW("OidbSvc.0x6d6_3", 1750, 3, oidb0x6d6ReqBody.toByteArray())
|
val fromServiceMsg = QQInterfaces.sendOidbAW("OidbSvc.0x6d6_3", 1750, 3, oidb0x6d6ReqBody.toByteArray())
|
||||||
?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
|
?: 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"))
|
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
||||||
}
|
}
|
||||||
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
||||||
@ -106,7 +106,7 @@ internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCorouti
|
|||||||
folderName = request.name
|
folderName = request.name
|
||||||
)
|
)
|
||||||
).toByteArray()) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
|
).toByteArray()) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
|
||||||
if (!fromServiceMsg.isSuccess) {
|
if (fromServiceMsg.wupBuffer == null) {
|
||||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
||||||
}
|
}
|
||||||
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
||||||
|
@ -1,33 +1,62 @@
|
|||||||
|
@file:OptIn(DelicateCoroutinesApi::class)
|
||||||
|
|
||||||
package moe.fuqiuluo.shamrock.internals
|
package moe.fuqiuluo.shamrock.internals
|
||||||
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
|
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
|
||||||
import io.kritor.Scene
|
import io.kritor.event.GroupApplyType
|
||||||
import io.kritor.contact
|
import io.kritor.event.GroupMemberBanType
|
||||||
|
import io.kritor.event.GroupMemberDecreasedType
|
||||||
|
import io.kritor.event.GroupMemberIncreasedType
|
||||||
import io.kritor.event.MessageEvent
|
import io.kritor.event.MessageEvent
|
||||||
|
import io.kritor.event.NoticeEvent
|
||||||
|
import io.kritor.event.NoticeType
|
||||||
|
import io.kritor.event.RequestType
|
||||||
|
import io.kritor.event.RequestsEvent
|
||||||
|
import io.kritor.event.Scene
|
||||||
|
import io.kritor.event.contact
|
||||||
|
import io.kritor.event.essenceMessageNotice
|
||||||
|
import io.kritor.event.friendApplyRequest
|
||||||
|
import io.kritor.event.friendFileComeNotice
|
||||||
|
import io.kritor.event.friendPokeNotice
|
||||||
|
import io.kritor.event.friendRecallNotice
|
||||||
|
import io.kritor.event.groupAdminChangedNotice
|
||||||
|
import io.kritor.event.groupApplyRequest
|
||||||
|
import io.kritor.event.groupFileComeNotice
|
||||||
|
import io.kritor.event.groupMemberBannedNotice
|
||||||
|
import io.kritor.event.groupMemberDecreasedNotice
|
||||||
|
import io.kritor.event.groupMemberIncreasedNotice
|
||||||
|
import io.kritor.event.groupPokeNotice
|
||||||
|
import io.kritor.event.groupRecallNotice
|
||||||
|
import io.kritor.event.groupSignNotice
|
||||||
|
import io.kritor.event.groupUniqueTitleChangedNotice
|
||||||
|
import io.kritor.event.groupWholeBanNotice
|
||||||
import io.kritor.event.messageEvent
|
import io.kritor.event.messageEvent
|
||||||
import io.kritor.sender
|
import io.kritor.event.noticeEvent
|
||||||
|
import io.kritor.event.requestsEvent
|
||||||
|
import io.kritor.event.sender
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.flow.FlowCollector
|
import kotlinx.coroutines.flow.FlowCollector
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.io.core.BytePacketBuilder
|
|
||||||
import qq.service.QQInterfaces
|
import qq.service.QQInterfaces
|
||||||
|
import qq.service.msg.toKritorMessages
|
||||||
|
|
||||||
internal object GlobalEventTransmitter: QQInterfaces() {
|
internal object GlobalEventTransmitter: QQInterfaces() {
|
||||||
private val messageEventFlow by lazy {
|
private val messageEventFlow by lazy {
|
||||||
MutableSharedFlow<Pair<MsgRecord, MessageEvent>>()
|
MutableSharedFlow<Pair<MsgRecord, MessageEvent>>()
|
||||||
}
|
}
|
||||||
//private val noticeEventFlow by lazy {
|
private val noticeEventFlow by lazy {
|
||||||
// MutableSharedFlow<NoticeEvent>()
|
MutableSharedFlow<NoticeEvent>()
|
||||||
//}
|
}
|
||||||
//private val requestEventFlow by lazy {
|
private val requestEventFlow by lazy {
|
||||||
// MutableSharedFlow<RequestEvent>()
|
MutableSharedFlow<RequestsEvent>()
|
||||||
//}
|
}
|
||||||
|
|
||||||
//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: RequestsEvent) = requestEventFlow.emit(requestEvent)
|
||||||
|
|
||||||
private suspend fun transMessageEvent(record: MsgRecord, message: MessageEvent) = messageEventFlow.emit(record to message)
|
private suspend fun transMessageEvent(record: MsgRecord, message: MessageEvent) = messageEventFlow.emit(record to message)
|
||||||
|
|
||||||
@ -51,6 +80,7 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
|||||||
this.uid = record.senderUid
|
this.uid = record.senderUid
|
||||||
this.nick = record.sendNickName
|
this.nick = record.sendNickName
|
||||||
}
|
}
|
||||||
|
this.elements.addAll(elements.toKritorMessages(record))
|
||||||
})
|
})
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -59,9 +89,23 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
|||||||
record: MsgRecord,
|
record: MsgRecord,
|
||||||
elements: ArrayList<MsgElement>,
|
elements: ArrayList<MsgElement>,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val botUin = app.longAccountUin
|
transMessageEvent(record, messageEvent {
|
||||||
var nickName = record.sendNickName
|
this.time = record.msgTime.toInt()
|
||||||
|
this.scene = Scene.FRIEND
|
||||||
|
this.messageId = record.msgId
|
||||||
|
this.messageSeq = record.msgSeq
|
||||||
|
this.contact = contact {
|
||||||
|
this.scene = scene
|
||||||
|
this.peer = record.senderUin.toString()
|
||||||
|
this.subPeer = record.senderUid
|
||||||
|
}
|
||||||
|
this.sender = sender {
|
||||||
|
this.uin = record.senderUin
|
||||||
|
this.uid = record.senderUid
|
||||||
|
this.nick = record.sendNickName
|
||||||
|
}
|
||||||
|
this.elements.addAll(elements.toKritorMessages(record))
|
||||||
|
})
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,9 +115,23 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
|||||||
groupCode: Long,
|
groupCode: Long,
|
||||||
fromNick: String,
|
fromNick: String,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val botUin = app.longAccountUin
|
transMessageEvent(record, messageEvent {
|
||||||
var nickName = record.sendNickName
|
this.time = record.msgTime.toInt()
|
||||||
|
this.scene = Scene.FRIEND
|
||||||
|
this.messageId = record.msgId
|
||||||
|
this.messageSeq = record.msgSeq
|
||||||
|
this.contact = contact {
|
||||||
|
this.scene = scene
|
||||||
|
this.peer = record.senderUin.toString()
|
||||||
|
this.subPeer = groupCode.toString()
|
||||||
|
}
|
||||||
|
this.sender = sender {
|
||||||
|
this.uin = record.senderUin
|
||||||
|
this.uid = record.senderUid
|
||||||
|
this.nick = record.sendNickName
|
||||||
|
}
|
||||||
|
this.elements.addAll(elements.toKritorMessages(record))
|
||||||
|
})
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,17 +139,30 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
|||||||
record: MsgRecord,
|
record: MsgRecord,
|
||||||
elements: ArrayList<MsgElement>,
|
elements: ArrayList<MsgElement>,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val botUin = app.longAccountUin
|
transMessageEvent(record, messageEvent {
|
||||||
var nickName = record.sendNickName
|
this.time = record.msgTime.toInt()
|
||||||
|
this.scene = Scene.GUILD
|
||||||
|
this.messageId = record.msgId
|
||||||
|
this.messageSeq = record.msgSeq
|
||||||
|
this.contact = contact {
|
||||||
|
this.scene = scene
|
||||||
|
this.peer = record.channelId.toString()
|
||||||
|
this.subPeer = record.guildId
|
||||||
|
}
|
||||||
|
this.sender = sender {
|
||||||
|
this.uin = record.senderUin
|
||||||
|
this.uid = record.senderUid
|
||||||
|
this.nick = record.sendNickName
|
||||||
|
}
|
||||||
|
this.elements.addAll(elements.toKritorMessages(record))
|
||||||
|
})
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
/**
|
/**
|
||||||
* 文件通知 通知器
|
* 文件通知 通知器
|
||||||
*/
|
**/
|
||||||
object FileNoticeTransmitter {
|
object FileNoticeTransmitter {
|
||||||
/**
|
/**
|
||||||
* 推送私聊文件事件
|
* 推送私聊文件事件
|
||||||
@ -106,23 +177,19 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
|||||||
expireTime: Long,
|
expireTime: Long,
|
||||||
url: String
|
url: String
|
||||||
): Boolean {
|
): Boolean {
|
||||||
pushNotice(NoticeEvent(
|
pushNotice(noticeEvent {
|
||||||
time = msgTime,
|
this.type = NoticeType.FRIEND_FILE_COME
|
||||||
selfId = app.longAccountUin,
|
this.time = msgTime.toInt()
|
||||||
postType = PostType.Notice,
|
this.friendFileCome = friendFileComeNotice {
|
||||||
type = NoticeType.PrivateUpload,
|
this.fileId = fileId
|
||||||
operatorId = userId,
|
this.fileName = fileName
|
||||||
userId = userId,
|
this.operator = userId
|
||||||
senderId = userId,
|
this.fileSize = fileSize
|
||||||
privateFile = PrivateFileMsg(
|
this.expireTime = expireTime.toInt()
|
||||||
id = fileId,
|
this.fileSubId = fileSubId
|
||||||
name = fileName,
|
this.url = url
|
||||||
size = fileSize,
|
}
|
||||||
url = url,
|
})
|
||||||
subId = fileSubId,
|
|
||||||
expire = expireTime
|
|
||||||
)
|
|
||||||
))
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,22 +206,19 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
|||||||
bizId: Int,
|
bizId: Int,
|
||||||
url: String
|
url: String
|
||||||
): Boolean {
|
): Boolean {
|
||||||
pushNotice(NoticeEvent(
|
pushNotice(noticeEvent {
|
||||||
time = msgTime,
|
this.type = NoticeType.GROUP_FILE_COME
|
||||||
selfId = app.longAccountUin,
|
this.time = msgTime.toInt()
|
||||||
postType = PostType.Notice,
|
this.groupFileCome = groupFileComeNotice {
|
||||||
type = NoticeType.GroupUpload,
|
this.groupId = groupId
|
||||||
operatorId = userId,
|
this.operator = userId
|
||||||
userId = userId,
|
this.fileId = uuid
|
||||||
groupId = groupId,
|
this.fileName = fileName
|
||||||
file = GroupFileMsg(
|
this.fileSize = fileSize
|
||||||
id = uuid,
|
this.biz = bizId
|
||||||
name = fileName,
|
this.url = url
|
||||||
size = fileSize,
|
}
|
||||||
busid = bizId.toLong(),
|
})
|
||||||
url = url
|
|
||||||
)
|
|
||||||
))
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,68 +228,80 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
|||||||
*/
|
*/
|
||||||
object GroupNoticeTransmitter {
|
object GroupNoticeTransmitter {
|
||||||
suspend fun transGroupSign(time: Long, target: Long, action: String?, rankImg: String?, groupCode: Long): Boolean {
|
suspend fun transGroupSign(time: Long, target: Long, action: String?, rankImg: String?, groupCode: Long): Boolean {
|
||||||
pushNotice(NoticeEvent(
|
pushNotice(noticeEvent {
|
||||||
time = time,
|
this.type = NoticeType.GROUP_SIGN
|
||||||
selfId = app.longAccountUin,
|
this.time = time.toInt()
|
||||||
postType = PostType.Notice,
|
this.groupSign = groupSignNotice {
|
||||||
type = NoticeType.Notify,
|
this.groupId = groupCode
|
||||||
subType = NoticeSubType.Sign,
|
this.targetUin = target
|
||||||
userId = target,
|
this.action = action ?: ""
|
||||||
groupId = groupCode,
|
this.suffix = ""
|
||||||
target = target,
|
this.rankImage = rankImg ?: ""
|
||||||
signDetail = SignDetail(
|
}
|
||||||
rankImg = rankImg,
|
})
|
||||||
action = action
|
|
||||||
)
|
|
||||||
))
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun transGroupPoke(time: Long, operation: Long, target: Long, action: String?, suffix: String?, actionImg: String?, groupCode: Long): Boolean {
|
suspend fun transGroupPoke(time: Long, operator: Long, target: Long, action: String?, suffix: String?, actionImg: String?, groupCode: Long): Boolean {
|
||||||
pushNotice(NoticeEvent(
|
pushNotice(noticeEvent {
|
||||||
time = time,
|
this.type = NoticeType.GROUP_POKE
|
||||||
selfId = app.longAccountUin,
|
this.time = time.toInt()
|
||||||
postType = PostType.Notice,
|
this.groupPoke = groupPokeNotice {
|
||||||
type = NoticeType.Notify,
|
this.action = action ?: ""
|
||||||
subType = NoticeSubType.Poke,
|
this.target = target
|
||||||
operatorId = operation,
|
this.operator = operator
|
||||||
userId = operation,
|
this.suffix = suffix ?: ""
|
||||||
groupId = groupCode,
|
this.actionImage = actionImg ?: ""
|
||||||
target = target,
|
}
|
||||||
pokeDetail = PokeDetail(
|
})
|
||||||
action = action,
|
|
||||||
suffix = suffix,
|
|
||||||
actionImg = actionImg
|
|
||||||
)
|
|
||||||
))
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun transGroupMemberNumChanged(
|
suspend fun transGroupMemberNumIncreased(
|
||||||
time: Long,
|
time: Long,
|
||||||
target: Long,
|
target: Long,
|
||||||
targetUid: String,
|
targetUid: String,
|
||||||
groupCode: Long,
|
groupCode: Long,
|
||||||
operator: Long,
|
operator: Long,
|
||||||
operatorUid: String,
|
operatorUid: String,
|
||||||
noticeType: NoticeType,
|
type: GroupMemberIncreasedType
|
||||||
noticeSubType: NoticeSubType
|
|
||||||
): Boolean {
|
): Boolean {
|
||||||
pushNotice(NoticeEvent(
|
pushNotice(noticeEvent {
|
||||||
time = time,
|
this.type = NoticeType.GROUP_MEMBER_INCREASE
|
||||||
selfId = app.longAccountUin,
|
this.time = time.toInt()
|
||||||
postType = PostType.Notice,
|
this.groupMemberIncrease = groupMemberIncreasedNotice {
|
||||||
type = noticeType,
|
this.groupId = groupCode
|
||||||
subType = noticeSubType,
|
this.operatorUid = operatorUid
|
||||||
operatorId = operator,
|
this.operatorUin = operator
|
||||||
userId = target,
|
this.targetUid = targetUid
|
||||||
senderId = operator,
|
this.targetUin = target
|
||||||
target = target,
|
this.type = type
|
||||||
groupId = groupCode,
|
}
|
||||||
targetUid = targetUid,
|
})
|
||||||
operatorUid = operatorUid,
|
return true
|
||||||
userUid = targetUid
|
}
|
||||||
))
|
|
||||||
|
suspend fun transGroupMemberNumDecreased(
|
||||||
|
time: Long,
|
||||||
|
target: Long,
|
||||||
|
targetUid: String,
|
||||||
|
groupCode: Long,
|
||||||
|
operator: Long,
|
||||||
|
operatorUid: String,
|
||||||
|
type: GroupMemberDecreasedType
|
||||||
|
): Boolean {
|
||||||
|
pushNotice(noticeEvent {
|
||||||
|
this.type = NoticeType.GROUP_MEMBER_INCREASE
|
||||||
|
this.time = time.toInt()
|
||||||
|
this.groupMemberDecrease = groupMemberDecreasedNotice {
|
||||||
|
this.groupId = groupCode
|
||||||
|
this.operatorUid = operatorUid
|
||||||
|
this.operatorUin = operator
|
||||||
|
this.targetUid = targetUid
|
||||||
|
this.targetUin = target
|
||||||
|
this.type = type
|
||||||
|
}
|
||||||
|
})
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,25 +312,39 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
|||||||
groupCode: Long,
|
groupCode: Long,
|
||||||
setAdmin: Boolean
|
setAdmin: Boolean
|
||||||
): Boolean {
|
): Boolean {
|
||||||
pushNotice(NoticeEvent(
|
pushNotice(noticeEvent {
|
||||||
time = msgTime,
|
this.type = NoticeType.GROUP_ADMIN_CHANGED
|
||||||
selfId = app.longAccountUin,
|
this.time = msgTime.toInt()
|
||||||
postType = PostType.Notice,
|
this.groupAdminChanged = groupAdminChangedNotice {
|
||||||
type = NoticeType.GroupAdminChange,
|
this.groupId = groupCode
|
||||||
subType = if (setAdmin) NoticeSubType.Set else NoticeSubType.UnSet,
|
this.targetUid = targetUid
|
||||||
operatorId = 0,
|
this.targetUin = target
|
||||||
userId = target,
|
this.isAdmin = setAdmin
|
||||||
userUid = targetUid,
|
}
|
||||||
target = target,
|
})
|
||||||
targetUid = targetUid,
|
return true
|
||||||
groupId = groupCode
|
}
|
||||||
))
|
|
||||||
|
suspend fun transGroupWholeBan(
|
||||||
|
msgTime: Long,
|
||||||
|
operator: Long,
|
||||||
|
groupCode: Long,
|
||||||
|
isOpen: Boolean
|
||||||
|
): Boolean {
|
||||||
|
pushNotice(noticeEvent {
|
||||||
|
this.type = NoticeType.GROUP_WHOLE_BAN
|
||||||
|
this.time = msgTime.toInt()
|
||||||
|
this.groupWholeBan = groupWholeBanNotice {
|
||||||
|
this.groupId = groupCode
|
||||||
|
this.isWholeBan = isOpen
|
||||||
|
this.operator = operator
|
||||||
|
}
|
||||||
|
})
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun transGroupBan(
|
suspend fun transGroupBan(
|
||||||
msgTime: Long,
|
msgTime: Long,
|
||||||
subType: NoticeSubType,
|
|
||||||
operator: Long,
|
operator: Long,
|
||||||
operatorUid: String,
|
operatorUid: String,
|
||||||
target: Long,
|
target: Long,
|
||||||
@ -262,43 +352,46 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
|||||||
groupCode: Long,
|
groupCode: Long,
|
||||||
duration: Int
|
duration: Int
|
||||||
): Boolean {
|
): Boolean {
|
||||||
pushNotice(NoticeEvent(
|
pushNotice(noticeEvent {
|
||||||
time = msgTime,
|
this.type = NoticeType.GROUP_MEMBER_BANNED
|
||||||
selfId = app.longAccountUin,
|
this.time = msgTime.toInt()
|
||||||
postType = PostType.Notice,
|
this.groupMemberBanned = groupMemberBannedNotice {
|
||||||
type = NoticeType.GroupBan,
|
this.groupId = groupCode
|
||||||
subType = subType,
|
this.operatorUid = operatorUid
|
||||||
operatorId = operator,
|
this.operatorUin = operator
|
||||||
userId = target,
|
this.targetUid = targetUid
|
||||||
senderId = operator,
|
this.targetUin = target
|
||||||
target = target,
|
this.duration = duration
|
||||||
groupId = groupCode,
|
this.type = if (duration > 0) GroupMemberBanType.BAN
|
||||||
duration = duration,
|
else GroupMemberBanType.LIFT_BAN
|
||||||
operatorUid = operatorUid,
|
}
|
||||||
targetUid = targetUid
|
})
|
||||||
))
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun transGroupMsgRecall(
|
suspend fun transGroupMsgRecall(
|
||||||
time: Long,
|
time: Long,
|
||||||
operator: Long,
|
operator: Long,
|
||||||
|
operatorUid: String,
|
||||||
target: Long,
|
target: Long,
|
||||||
|
targetUid: String,
|
||||||
groupCode: Long,
|
groupCode: Long,
|
||||||
msgHash: Int,
|
msgId: Long,
|
||||||
tipText: String
|
tipText: String
|
||||||
): Boolean {
|
): Boolean {
|
||||||
pushNotice(NoticeEvent(
|
pushNotice(noticeEvent {
|
||||||
time = time,
|
this.type = NoticeType.GROUP_RECALL
|
||||||
selfId = app.longAccountUin,
|
this.time = time.toInt()
|
||||||
postType = PostType.Notice,
|
this.groupRecall = groupRecallNotice {
|
||||||
type = NoticeType.GroupRecall,
|
this.groupId = groupCode
|
||||||
operatorId = operator,
|
this.operatorUid = operatorUid
|
||||||
userId = target,
|
this.operatorUin = operator
|
||||||
msgId = msgHash,
|
this.targetUid = targetUid
|
||||||
tip = tipText,
|
this.targetUin = target
|
||||||
groupId = groupCode
|
this.messageId = msgId
|
||||||
))
|
this.tipText = tipText
|
||||||
|
}
|
||||||
|
})
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -309,16 +402,7 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
|||||||
newCard: String,
|
newCard: String,
|
||||||
groupId: Long
|
groupId: Long
|
||||||
): Boolean {
|
): Boolean {
|
||||||
pushNotice(NoticeEvent(
|
|
||||||
time = time,
|
|
||||||
selfId = app.longAccountUin,
|
|
||||||
postType = PostType.Notice,
|
|
||||||
type = NoticeType.GroupCard,
|
|
||||||
userId = targetId,
|
|
||||||
cardNew = newCard,
|
|
||||||
cardOld = oldCard,
|
|
||||||
groupId = groupId
|
|
||||||
))
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,16 +412,15 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
|||||||
title: String,
|
title: String,
|
||||||
groupId: Long
|
groupId: Long
|
||||||
): Boolean {
|
): Boolean {
|
||||||
pushNotice(NoticeEvent(
|
pushNotice(noticeEvent {
|
||||||
time = time,
|
this.type = NoticeType.GROUP_MEMBER_UNIQUE_TITLE_CHANGED
|
||||||
selfId = app.longAccountUin,
|
this.time = time.toInt()
|
||||||
postType = PostType.Notice,
|
this.groupMemberUniqueTitleChanged = groupUniqueTitleChangedNotice {
|
||||||
type = NoticeType.Notify,
|
this.groupId = groupId
|
||||||
userId = targetId,
|
this.target = targetId
|
||||||
groupId = groupId,
|
this.title = title
|
||||||
title = title,
|
}
|
||||||
subType = NoticeSubType.Title
|
})
|
||||||
))
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -345,21 +428,21 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
|||||||
time: Long,
|
time: Long,
|
||||||
senderUin: Long,
|
senderUin: Long,
|
||||||
operatorUin: Long,
|
operatorUin: Long,
|
||||||
msgId: Int,
|
msgId: Long,
|
||||||
groupId: Long,
|
groupId: Long,
|
||||||
subType: NoticeSubType
|
subType: UInt
|
||||||
): Boolean {
|
): Boolean {
|
||||||
pushNotice(NoticeEvent(
|
pushNotice(noticeEvent {
|
||||||
time = time,
|
this.type = NoticeType.GROUP_ESSENCE_CHANGED
|
||||||
selfId = app.longAccountUin,
|
this.time = time.toInt()
|
||||||
postType = PostType.Notice,
|
this.groupEssenceChanged = essenceMessageNotice {
|
||||||
type = NoticeType.Essence,
|
this.groupId = groupId
|
||||||
senderId = senderUin,
|
this.messageId = msgId
|
||||||
groupId = groupId,
|
this.sender = senderUin
|
||||||
operatorId = operatorUin,
|
this.operator = operatorUin
|
||||||
msgId = msgId,
|
this.subType = subType.toInt()
|
||||||
subType = subType
|
}
|
||||||
))
|
})
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -368,37 +451,31 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
|||||||
* 私聊通知 通知器
|
* 私聊通知 通知器
|
||||||
*/
|
*/
|
||||||
object PrivateNoticeTransmitter {
|
object PrivateNoticeTransmitter {
|
||||||
suspend fun transPrivatePoke(msgTime: Long, operation: Long, target: Long, action: String?, suffix: String?, actionImg: String?): Boolean {
|
suspend fun transPrivatePoke(msgTime: Long, operator: Long, target: Long, action: String?, suffix: String?, actionImg: String?): Boolean {
|
||||||
pushNotice(NoticeEvent(
|
pushNotice(noticeEvent {
|
||||||
time = msgTime,
|
this.type = NoticeType.FRIEND_POKE
|
||||||
selfId = app.longAccountUin,
|
this.time = msgTime.toInt()
|
||||||
postType = PostType.Notice,
|
this.friendPoke = friendPokeNotice {
|
||||||
type = NoticeType.Notify,
|
this.action = action ?: ""
|
||||||
subType = NoticeSubType.Poke,
|
this.target = target
|
||||||
operatorId = operation,
|
this.operator = operator
|
||||||
userId = operation,
|
this.suffix = suffix ?: ""
|
||||||
senderId = operation,
|
this.actionImage = actionImg ?: ""
|
||||||
target = target,
|
}
|
||||||
pokeDetail = PokeDetail(
|
})
|
||||||
actionImg = actionImg,
|
|
||||||
action = action,
|
|
||||||
suffix = suffix
|
|
||||||
)
|
|
||||||
))
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun transPrivateRecall(time: Long, operation: Long, msgHashId: Int, tipText: String): Boolean {
|
suspend fun transPrivateRecall(time: Long, operator: Long, msgId: Long, tipText: String): Boolean {
|
||||||
pushNotice(NoticeEvent(
|
pushNotice(noticeEvent {
|
||||||
time = time,
|
this.type = NoticeType.FRIEND_RECALL
|
||||||
selfId = app.longAccountUin,
|
this.time = time.toInt()
|
||||||
postType = PostType.Notice,
|
this.friendRecall = friendRecallNotice {
|
||||||
type = NoticeType.FriendRecall,
|
this.operator = operator
|
||||||
operatorId = operation,
|
this.messageId = msgId
|
||||||
userId = operation,
|
this.tipText = tipText
|
||||||
msgId = msgHashId,
|
}
|
||||||
tip = tipText
|
})
|
||||||
))
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,16 +485,16 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
|||||||
* 请求 通知器
|
* 请求 通知器
|
||||||
*/
|
*/
|
||||||
object RequestTransmitter {
|
object RequestTransmitter {
|
||||||
suspend fun transFriendApp(time: Long, operation: Long, tipText: String, flag: String): Boolean {
|
suspend fun transFriendApp(time: Long, operator: Long, tipText: String, flag: String): Boolean {
|
||||||
pushRequest(RequestEvent(
|
pushRequest(requestsEvent {
|
||||||
time = time,
|
this.type = RequestType.FRIEND_APPLY
|
||||||
selfId = app.longAccountUin,
|
this.time = time.toInt()
|
||||||
postType = PostType.Request,
|
this.friendApply = friendApplyRequest {
|
||||||
type = RequestType.Friend,
|
this.applierUin = operator
|
||||||
userId = operation,
|
this.message = tipText
|
||||||
comment = tipText,
|
this.flag = flag
|
||||||
flag = flag
|
}
|
||||||
))
|
})
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -428,23 +505,23 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
|||||||
reason: String,
|
reason: String,
|
||||||
groupCode: Long,
|
groupCode: Long,
|
||||||
flag: String,
|
flag: String,
|
||||||
subType: RequestSubType
|
type: GroupApplyType
|
||||||
): Boolean {
|
): Boolean {
|
||||||
pushRequest(RequestEvent(
|
pushRequest(requestsEvent {
|
||||||
time = time,
|
this.type = RequestType.GROUP_APPLY
|
||||||
selfId = app.longAccountUin,
|
this.time = time.toInt()
|
||||||
postType = PostType.Request,
|
this.groupApply = groupApplyRequest {
|
||||||
type = RequestType.Group,
|
this.applierUid = applierUid
|
||||||
userId = applier,
|
this.applierUin = applier
|
||||||
userUid = applierUid,
|
this.groupId = groupCode
|
||||||
comment = reason,
|
this.reason = reason
|
||||||
groupId = groupCode,
|
this.flag = flag
|
||||||
subType = subType,
|
this.type = type
|
||||||
flag = flag
|
}
|
||||||
))
|
})
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
|
|
||||||
suspend inline fun onMessageEvent(collector: FlowCollector<Pair<MsgRecord, MessageEvent>>) {
|
suspend inline fun onMessageEvent(collector: FlowCollector<Pair<MsgRecord, MessageEvent>>) {
|
||||||
messageEventFlow.collect {
|
messageEventFlow.collect {
|
||||||
@ -454,7 +531,6 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
suspend inline fun onNoticeEvent(collector: FlowCollector<NoticeEvent>) {
|
suspend inline fun onNoticeEvent(collector: FlowCollector<NoticeEvent>) {
|
||||||
noticeEventFlow.collect {
|
noticeEventFlow.collect {
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
@ -463,11 +539,11 @@ internal object GlobalEventTransmitter: QQInterfaces() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend inline fun onRequestEvent(collector: FlowCollector<RequestEvent>) {
|
suspend inline fun onRequestEvent(collector: FlowCollector<RequestsEvent>) {
|
||||||
requestEventFlow.collect {
|
requestEventFlow.collect {
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
collector.emit(it)
|
collector.emit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
}
|
}
|
@ -7,6 +7,7 @@ import android.content.pm.PackageManager
|
|||||||
import android.content.pm.VersionedPackage
|
import android.content.pm.VersionedPackage
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
import com.tencent.qphone.base.remote.ToServiceMsg
|
||||||
import de.robv.android.xposed.XC_MethodReplacement
|
import de.robv.android.xposed.XC_MethodReplacement
|
||||||
import de.robv.android.xposed.XSharedPreferences
|
import de.robv.android.xposed.XSharedPreferences
|
||||||
import de.robv.android.xposed.XposedHelpers
|
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.LuoClassloader
|
||||||
import moe.fuqiuluo.shamrock.xposed.loader.NativeLoader
|
import moe.fuqiuluo.shamrock.xposed.loader.NativeLoader
|
||||||
import moe.fuqiuluo.symbols.XposedHook
|
import moe.fuqiuluo.symbols.XposedHook
|
||||||
|
import mqq.app.MobileQQ
|
||||||
|
import qq.service.QQInterfaces
|
||||||
|
|
||||||
@XposedHook(priority = 0)
|
@XposedHook(priority = 0)
|
||||||
class AntiDetection: IAction {
|
class AntiDetection: IAction {
|
||||||
@ -36,6 +39,17 @@ class AntiDetection: IAction {
|
|||||||
if (ShamrockConfig[AntiJvmTrace])
|
if (ShamrockConfig[AntiJvmTrace])
|
||||||
antiTrace()
|
antiTrace()
|
||||||
antiMemoryWalking()
|
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) {
|
private fun antiGetPackageGidsDetection(ctx: Context) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
495
xposed/src/main/java/qq/service/bdh/NtV2RichMediaSvc.kt
Normal file
495
xposed/src/main/java/qq/service/bdh/NtV2RichMediaSvc.kt
Normal file
@ -0,0 +1,495 @@
|
|||||||
|
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.kernel.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.tools.hex2ByteArray
|
||||||
|
import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
|
||||||
|
import moe.fuqiuluo.shamrock.tools.slice
|
||||||
|
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.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)
|
||||||
|
|
||||||
|
private 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取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}"))
|
||||||
|
}
|
||||||
|
fromServiceMsg.wupBuffer.slice(4).decodeProtobuf<TrpcOidb>().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,
|
||||||
|
sceneBuilder: suspend SceneInfo.() -> Unit
|
||||||
|
): Result<UploadRsp> {
|
||||||
|
return runCatching {
|
||||||
|
requestUploadNtPic(file, md5, sha, name, width, height, sceneBuilder).getOrThrow()
|
||||||
|
}.onFailure {
|
||||||
|
if (retryCnt > 0) {
|
||||||
|
return requestUploadNtPic(file, md5, sha, name, width, height, retryCnt - 1, sceneBuilder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun requestUploadNtPic(
|
||||||
|
file: File,
|
||||||
|
md5: String,
|
||||||
|
sha: String,
|
||||||
|
name: String,
|
||||||
|
width: UInt,
|
||||||
|
height: UInt,
|
||||||
|
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 = 1u,
|
||||||
|
clientSeq = Random.nextUInt(),
|
||||||
|
noNeedCompatMsg = false
|
||||||
|
)
|
||||||
|
).toByteArray()
|
||||||
|
val fromServiceMsg = sendOidbAW("OidbSvcTrpcTcp.0x11c5_100", 4549, 100, req, true, timeout = 3.seconds)
|
||||||
|
if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) {
|
||||||
|
return Result.failure(Exception("unable to request upload nt pic"))
|
||||||
|
}
|
||||||
|
val rspBuffer = fromServiceMsg.wupBuffer.slice(4).decodeProtobuf<TrpcOidb>().buffer
|
||||||
|
val rsp = rspBuffer.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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
429
xposed/src/main/java/qq/service/bdh/RichProtoSvc.kt
Normal file
429
xposed/src/main/java/qq/service/bdh/RichProtoSvc.kt
Normal file
@ -0,0 +1,429 @@
|
|||||||
|
@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 kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
|
import moe.fuqiuluo.shamrock.tools.slice
|
||||||
|
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 tencent.im.oidb.oidb_sso
|
||||||
|
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 = oidb_sso.OIDBSSOPkg()
|
||||||
|
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||||
|
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 = oidb_sso.OIDBSSOPkg()
|
||||||
|
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||||
|
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 = oidb_sso.OIDBSSOPkg()
|
||||||
|
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||||
|
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 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
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,
|
||||||
|
)
|
@ -33,7 +33,7 @@ internal object GroupFileHelper: QQInterfaces() {
|
|||||||
it.uint32_bus_id.set(0)
|
it.uint32_bus_id.set(0)
|
||||||
})
|
})
|
||||||
}.toByteArray()) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
|
}.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"))
|
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
||||||
}
|
}
|
||||||
val fileCnt: Int
|
val fileCnt: Int
|
||||||
@ -104,7 +104,7 @@ internal object GroupFileHelper: QQInterfaces() {
|
|||||||
uint32_show_onlinedoc_folder.set(0)
|
uint32_show_onlinedoc_folder.set(0)
|
||||||
})
|
})
|
||||||
}.toByteArray(), timeout = 15.seconds) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
|
}.toByteArray(), timeout = 15.seconds) ?: 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"))
|
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
||||||
}
|
}
|
||||||
val files = arrayListOf<File>()
|
val files = arrayListOf<File>()
|
||||||
|
@ -3,11 +3,15 @@ package qq.service.friend
|
|||||||
import com.tencent.mobileqq.data.Friends
|
import com.tencent.mobileqq.data.Friends
|
||||||
import com.tencent.mobileqq.friend.api.IFriendDataService
|
import com.tencent.mobileqq.friend.api.IFriendDataService
|
||||||
import com.tencent.mobileqq.friend.api.IFriendHandlerService
|
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.GlobalScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import moe.fuqiuluo.shamrock.tools.slice
|
||||||
import qq.service.QQInterfaces
|
import qq.service.QQInterfaces
|
||||||
|
import tencent.mobileim.structmsg.structmsg
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
internal object FriendHelper: QQInterfaces() {
|
internal object FriendHelper: QQInterfaces() {
|
||||||
@ -21,6 +25,69 @@ internal object FriendHelper: QQInterfaces() {
|
|||||||
return Result.success(service.allFriends)
|
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 {
|
private suspend fun requestFriendList(dataService: IFriendDataService): Boolean {
|
||||||
val service = app.getRuntimeService(IFriendHandlerService::class.java, "all")
|
val service = app.getRuntimeService(IFriendHandlerService::class.java, "all")
|
||||||
service.requestFriendList(true, 0)
|
service.requestFriendList(true, 0)
|
||||||
|
@ -37,6 +37,7 @@ import tencent.im.oidb.cmd0x8fc.Oidb_0x8fc
|
|||||||
import tencent.im.oidb.cmd0xed3.oidb_cmd0xed3
|
import tencent.im.oidb.cmd0xed3.oidb_cmd0xed3
|
||||||
import tencent.im.oidb.oidb_sso
|
import tencent.im.oidb.oidb_sso
|
||||||
import tencent.im.troop.honor.troop_honor
|
import tencent.im.troop.honor.troop_honor
|
||||||
|
import tencent.mobileim.structmsg.structmsg
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
import java.lang.reflect.Modifier
|
import java.lang.reflect.Modifier
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
@ -217,6 +218,106 @@ internal object GroupHelper: QQInterfaces() {
|
|||||||
sendOidb("OidbSvc.0x55c_1", 1372, 1, array)
|
sendOidb("OidbSvc.0x55c_1", 1372, 1, array)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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: Long, userId: Long, title: String) {
|
suspend fun setGroupUniqueTitle(groupId: Long, userId: Long, title: String) {
|
||||||
val localMemberInfo = getTroopMemberInfoByUin(groupId, userId, true).getOrThrow()
|
val localMemberInfo = getTroopMemberInfoByUin(groupId, userId, true).getOrThrow()
|
||||||
val req = Oidb_0x8fc.ReqBody()
|
val req = Oidb_0x8fc.ReqBody()
|
||||||
@ -269,7 +370,7 @@ internal object GroupHelper: QQInterfaces() {
|
|||||||
uint32_shutup_timestap.set(0)
|
uint32_shutup_timestap.set(0)
|
||||||
})
|
})
|
||||||
}.toByteArray()) ?: return Result.failure(RuntimeException("[oidb] timeout"))
|
}.toByteArray()) ?: return Result.failure(RuntimeException("[oidb] timeout"))
|
||||||
if (!fromServiceMsg.isSuccess) {
|
if (fromServiceMsg.wupBuffer == null) {
|
||||||
return Result.failure(RuntimeException("[oidb] failed"))
|
return Result.failure(RuntimeException("[oidb] failed"))
|
||||||
}
|
}
|
||||||
val body = oidb_sso.OIDBSSOPkg()
|
val body = oidb_sso.OIDBSSOPkg()
|
||||||
@ -291,7 +392,7 @@ internal object GroupHelper: QQInterfaces() {
|
|||||||
uint64_uin.set(app.longAccountUin)
|
uint64_uin.set(app.longAccountUin)
|
||||||
uint64_group_code.set(groupId)
|
uint64_group_code.set(groupId)
|
||||||
}.toByteArray(), trpc = true) ?: return Result.failure(RuntimeException("[oidb] timeout"))
|
}.toByteArray(), trpc = true) ?: return Result.failure(RuntimeException("[oidb] timeout"))
|
||||||
if (!fromServiceMsg.isSuccess) {
|
if (fromServiceMsg.wupBuffer == null) {
|
||||||
return Result.failure(RuntimeException("[oidb] failed"))
|
return Result.failure(RuntimeException("[oidb] failed"))
|
||||||
}
|
}
|
||||||
val body = oidb_sso.OIDBSSOPkg()
|
val body = oidb_sso.OIDBSSOPkg()
|
||||||
@ -311,7 +412,7 @@ internal object GroupHelper: QQInterfaces() {
|
|||||||
toServiceMsg.extraData.putBoolean("is_admin", false)
|
toServiceMsg.extraData.putBoolean("is_admin", false)
|
||||||
toServiceMsg.extraData.putInt("from", 0)
|
toServiceMsg.extraData.putInt("from", 0)
|
||||||
val fromServiceMsg = sendToServiceMsgAW(toServiceMsg) ?: return@timeout Result.failure(Exception("获取群信息超时"))
|
val fromServiceMsg = sendToServiceMsgAW(toServiceMsg) ?: return@timeout Result.failure(Exception("获取群信息超时"))
|
||||||
if (!fromServiceMsg.isSuccess) {
|
if (fromServiceMsg.wupBuffer == null) {
|
||||||
return@timeout Result.failure(Exception("获取群信息失败"))
|
return@timeout Result.failure(Exception("获取群信息失败"))
|
||||||
}
|
}
|
||||||
val uniPacket = UniPacket(true)
|
val uniPacket = UniPacket(true)
|
||||||
@ -393,7 +494,7 @@ internal object GroupHelper: QQInterfaces() {
|
|||||||
req.uint32_client_type.set(1)
|
req.uint32_client_type.set(1)
|
||||||
req.uint32_rich_card_name_ver.set(1)
|
req.uint32_rich_card_name_ver.set(1)
|
||||||
val fromServiceMsg = sendBufferAW("group_member_card.get_group_member_card_info", true, req.toByteArray())
|
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()
|
val rsp = group_member_info.RspBody()
|
||||||
rsp.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
rsp.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||||
if (rsp.msg_meminfo.str_location.has()) {
|
if (rsp.msg_meminfo.str_location.has()) {
|
||||||
|
@ -1,49 +1,22 @@
|
|||||||
|
@file:OptIn(DelicateCoroutinesApi::class)
|
||||||
|
|
||||||
package qq.service.internals
|
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.MsgConstant
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
|
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgSetting
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
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 kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import moe.fuqiuluo.shamrock.helper.Level
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
import moe.fuqiuluo.shamrock.internals.GlobalEventTransmitter
|
import moe.fuqiuluo.shamrock.internals.GlobalEventTransmitter
|
||||||
|
import qq.service.bdh.RichProtoSvc
|
||||||
|
import qq.service.kernel.SimpleKernelMsgListener
|
||||||
import qq.service.msg.MessageHelper
|
import qq.service.msg.MessageHelper
|
||||||
|
|
||||||
object AioListener: IKernelMsgListener {
|
object AioListener: SimpleKernelMsgListener() {
|
||||||
override fun onRecvMsg(msgs: ArrayList<MsgRecord>) {
|
override fun onRecvMsg(records: ArrayList<MsgRecord>) {
|
||||||
msgs.forEach {
|
records.forEach {
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
try {
|
try {
|
||||||
onMsg(it)
|
onMsg(it)
|
||||||
@ -57,6 +30,8 @@ object AioListener: IKernelMsgListener {
|
|||||||
private suspend fun onMsg(record: MsgRecord) {
|
private suspend fun onMsg(record: MsgRecord) {
|
||||||
when (record.chatType) {
|
when (record.chatType) {
|
||||||
MsgConstant.KCHATTYPEGROUP -> {
|
MsgConstant.KCHATTYPEGROUP -> {
|
||||||
|
if (record.senderUin == 0L) return
|
||||||
|
|
||||||
LogCenter.log("群消息(group = ${record.peerName}(${record.peerUin}), uin = ${record.senderUin}, id = ${record.msgId})")
|
LogCenter.log("群消息(group = ${record.peerName}(${record.peerUin}), uin = ${record.senderUin}, id = ${record.msgId})")
|
||||||
|
|
||||||
if (!GlobalEventTransmitter.MessageTransmitter.transGroupMessage(record, record.elements)) {
|
if (!GlobalEventTransmitter.MessageTransmitter.transGroupMessage(record, record.elements)) {
|
||||||
@ -104,274 +79,62 @@ 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>?) {
|
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 userId = record.senderUin
|
||||||
|
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, userId, 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 userId = record.senderUin
|
||||||
|
val fileMsg = record.elements.firstOrNull {
|
||||||
override fun onGrabPasswordRedBag(
|
it.elementType == MsgConstant.KELEMTYPEFILE
|
||||||
i2: Int,
|
}?.fileElement ?: kotlin.run {
|
||||||
str: String?,
|
LogCenter.log("消息为群聊文件消息但不包含文件消息,来自:${record.peerUin}", Level.WARN)
|
||||||
i3: Int,
|
return
|
||||||
recvdOrder: RecvdOrder?,
|
}
|
||||||
msgRecord: MsgRecord?
|
//val fileMd5 = fileMsg.fileMd5
|
||||||
) {
|
val fileName = fileMsg.fileName
|
||||||
|
val fileSize = fileMsg.fileSize
|
||||||
}
|
val uuid = fileMsg.fileUuid
|
||||||
|
val bizId = fileMsg.fileBizId
|
||||||
override fun onKickedOffLine(kickedInfo: KickedInfo?) {
|
|
||||||
LogCenter.log("onKickedOffLine($kickedInfo)")
|
val url = RichProtoSvc.getGroupFileDownUrl(record.peerUin, uuid, bizId)
|
||||||
}
|
|
||||||
|
if (!GlobalEventTransmitter.FileNoticeTransmitter
|
||||||
override fun onRichMediaUploadComplete(notifyInfo: FileTransNotifyInfo) {
|
.transGroupFileEvent(record.msgTime, userId, groupId, uuid, fileName, fileSize, bizId, url)
|
||||||
LogCenter.log({ "[BDH] 资源上传完成(${notifyInfo.trasferStatus}, ${notifyInfo.fileId}, ${notifyInfo.msgId}, ${notifyInfo.commonFileInfo})" }, Level.DEBUG)
|
) {
|
||||||
}
|
LogCenter.log("群聊文件消息推送失败 -> FileNoticeTransmitter", Level.WARN)
|
||||||
|
}
|
||||||
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?) {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -52,8 +52,12 @@ internal object MSFHandler {
|
|||||||
|
|
||||||
fun onPush(fromServiceMsg: FromServiceMsg) {
|
fun onPush(fromServiceMsg: FromServiceMsg) {
|
||||||
val cmd = fromServiceMsg.serviceCmd
|
val cmd = fromServiceMsg.serviceCmd
|
||||||
val push = mPushHandlers[cmd]
|
if (cmd == "trpc.msg.olpush.OlPushService.MsgPush") {
|
||||||
push?.invoke(fromServiceMsg)
|
PrimitiveListener.onPush(fromServiceMsg)
|
||||||
|
} else {
|
||||||
|
val push = mPushHandlers[cmd]
|
||||||
|
push?.invoke(fromServiceMsg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onResp(toServiceMsg: ToServiceMsg, fromServiceMsg: FromServiceMsg) {
|
fun onResp(toServiceMsg: ToServiceMsg, fromServiceMsg: FromServiceMsg) {
|
||||||
|
668
xposed/src/main/java/qq/service/internals/PrimitiveListener.kt
Normal file
668
xposed/src/main/java/qq/service/internals/PrimitiveListener.kt
Normal file
@ -0,0 +1,668 @@
|
|||||||
|
@file:OptIn(DelicateCoroutinesApi::class)
|
||||||
|
package qq.service.internals
|
||||||
|
|
||||||
|
import com.tencent.mobileqq.qroute.QRoute
|
||||||
|
import com.tencent.qphone.base.remote.FromServiceMsg
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||||
|
import com.tencent.qqnt.msg.api.IMsgService
|
||||||
|
import io.kritor.event.GroupApplyType
|
||||||
|
import io.kritor.event.GroupMemberDecreasedType
|
||||||
|
import io.kritor.event.GroupMemberIncreasedType
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
|
import kotlinx.io.core.ByteReadPacket
|
||||||
|
import kotlinx.io.core.discardExact
|
||||||
|
import kotlinx.io.core.readBytes
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
|
import moe.fuqiuluo.shamrock.internals.GlobalEventTransmitter
|
||||||
|
import moe.fuqiuluo.shamrock.tools.asJsonObject
|
||||||
|
import moe.fuqiuluo.shamrock.tools.asString
|
||||||
|
import moe.fuqiuluo.shamrock.tools.readBuf32Long
|
||||||
|
import moe.fuqiuluo.shamrock.tools.slice
|
||||||
|
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||||
|
import protobuf.message.ContentHead
|
||||||
|
import protobuf.message.MsgBody
|
||||||
|
import protobuf.message.ResponseHead
|
||||||
|
import protobuf.push.C2CCommonTipsEvent
|
||||||
|
import protobuf.push.C2CRecallEvent
|
||||||
|
import protobuf.push.FriendApplyEvent
|
||||||
|
import protobuf.push.GroupAdminChangeEvent
|
||||||
|
import protobuf.push.GroupApplyEvent
|
||||||
|
import protobuf.push.GroupBanEvent
|
||||||
|
import protobuf.push.GroupCommonTipsEvent
|
||||||
|
import protobuf.push.GroupInviteEvent
|
||||||
|
import protobuf.push.GroupInvitedApplyEvent
|
||||||
|
import protobuf.push.GroupListChangeEvent
|
||||||
|
import protobuf.push.MessagePush
|
||||||
|
import protobuf.push.MessagePushClientInfo
|
||||||
|
import qq.service.QQInterfaces
|
||||||
|
import qq.service.contact.ContactHelper
|
||||||
|
import qq.service.friend.FriendHelper.requestFriendSystemMsgNew
|
||||||
|
import qq.service.group.GroupHelper
|
||||||
|
import qq.service.group.GroupHelper.requestGroupSystemMsgNew
|
||||||
|
import qq.service.msg.MessageHelper
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
|
internal object PrimitiveListener {
|
||||||
|
|
||||||
|
fun onPush(fromServiceMsg: FromServiceMsg) {
|
||||||
|
if (fromServiceMsg.wupBuffer == null) return
|
||||||
|
try {
|
||||||
|
val push = fromServiceMsg.wupBuffer.slice(4)
|
||||||
|
.decodeProtobuf<MessagePush>()
|
||||||
|
GlobalScope.launch {
|
||||||
|
onMsgPush(push)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LogCenter.log(e.stackTraceToString(), Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun onMsgPush(push: MessagePush) {
|
||||||
|
if (
|
||||||
|
push.msgBody == null ||
|
||||||
|
push.msgBody!!.contentHead == null ||
|
||||||
|
push.msgBody!!.body == null ||
|
||||||
|
push.msgBody!!.contentHead!!.msgTime == null
|
||||||
|
) return
|
||||||
|
val msgBody = push.msgBody!!
|
||||||
|
val contentHead = msgBody.contentHead!!
|
||||||
|
val msgType = contentHead.msgType
|
||||||
|
val subType = contentHead.msgSubType
|
||||||
|
val msgTime = contentHead.msgTime!!
|
||||||
|
val body = msgBody.body!!
|
||||||
|
try {
|
||||||
|
when (msgType) {
|
||||||
|
33 -> onGroupMemIncreased(msgTime, body)
|
||||||
|
34 -> onGroupMemberDecreased(msgTime, body)
|
||||||
|
44 -> onGroupAdminChange(msgTime, body)
|
||||||
|
82 -> onGroupMessage(msgTime, body)
|
||||||
|
84 -> onGroupApply(msgTime, contentHead, body)
|
||||||
|
87 -> onInviteGroup(msgTime, msgBody.msgHead!!, body)
|
||||||
|
528 -> when (subType) {
|
||||||
|
35 -> onFriendApply(msgTime, push.clientInfo!!, body)
|
||||||
|
39 -> onCardChange(msgTime, body)
|
||||||
|
68 -> onGroupApply(msgTime, contentHead, body)
|
||||||
|
138 -> onC2CRecall(msgTime, body)
|
||||||
|
290 -> onC2CPoke(msgTime, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
732 -> when (subType) {
|
||||||
|
12 -> onGroupBan(msgTime, body)
|
||||||
|
16 -> onGroupUniqueTitleChange(msgTime, body)
|
||||||
|
17 -> onGroupRecall(msgTime, body)
|
||||||
|
20 -> onGroupCommonTips(msgTime, body)
|
||||||
|
21 -> onEssenceMessage(msgTime, push.clientInfo, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LogCenter.log("onMsgPush(msgType: $msgType, subType: $subType): " + e.stackTraceToString(), Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onGroupMessage(msgTime: Long, body: MsgBody) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun onC2CPoke(msgTime: Long, body: MsgBody) {
|
||||||
|
val event = body.msgContent!!.decodeProtobuf<C2CCommonTipsEvent>()
|
||||||
|
if (event.params == null) return
|
||||||
|
|
||||||
|
val params = event.params!!.associate {
|
||||||
|
it.key to it.value
|
||||||
|
}
|
||||||
|
|
||||||
|
val target = params["uin_str2"] ?: return
|
||||||
|
val operation = params["uin_str1"] ?: return
|
||||||
|
val suffix = params["suffix_str"] ?: ""
|
||||||
|
val actionImg = params["action_img_url"] ?: ""
|
||||||
|
val action = params["alt_str1"] ?: ""
|
||||||
|
|
||||||
|
LogCenter.log("私聊戳一戳: $operation $action $target $suffix")
|
||||||
|
|
||||||
|
if (!GlobalEventTransmitter.PrivateNoticeTransmitter
|
||||||
|
.transPrivatePoke(msgTime, operation.toLong(), target.toLong(), action, suffix, actionImg)
|
||||||
|
) {
|
||||||
|
LogCenter.log("私聊戳一戳推送失败!", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun onFriendApply(
|
||||||
|
msgTime: Long,
|
||||||
|
clientInfo: MessagePushClientInfo,
|
||||||
|
body: MsgBody
|
||||||
|
) {
|
||||||
|
val event = body.msgContent!!.decodeProtobuf<FriendApplyEvent>()
|
||||||
|
if (event.head == null) return
|
||||||
|
val head = event.head!!
|
||||||
|
val applierUid = head.applierUid
|
||||||
|
val msg = head.applyMsg ?: ""
|
||||||
|
val source = head.source ?: ""
|
||||||
|
var applier = ContactHelper.getUinByUidAsync(applierUid).toLong()
|
||||||
|
if (applier == 0L) {
|
||||||
|
applier = clientInfo.liteHead?.sender?.toLong() ?: 0
|
||||||
|
}
|
||||||
|
val src = head.srcId
|
||||||
|
val subSrc = head.subSrc
|
||||||
|
val flag: String = try {
|
||||||
|
val reqs = requestFriendSystemMsgNew(20, 0, 0)
|
||||||
|
val req = reqs?.first {
|
||||||
|
it.msg_time.get() == msgTime
|
||||||
|
}
|
||||||
|
val seq = req?.msg_seq?.get()
|
||||||
|
"$seq;$src;$subSrc;$applier"
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
"$msgTime;$src;$subSrc;$applier"
|
||||||
|
}
|
||||||
|
LogCenter.log("来自$applier 的好友申请:$msg ($source)")
|
||||||
|
if (!GlobalEventTransmitter.RequestTransmitter
|
||||||
|
.transFriendApp(msgTime, applier, msg, flag)
|
||||||
|
) {
|
||||||
|
LogCenter.log("好友申请推送失败!", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private suspend fun onCardChange(msgTime: Long, body: MsgBody) {
|
||||||
|
// val event = runCatching {
|
||||||
|
// body.msgContent!!.decodeProtobuf<GroupCardChangeEvent>()
|
||||||
|
// }.getOrElse {
|
||||||
|
// val readPacket = ByteReadPacket(body.msgContent!!)
|
||||||
|
// readPacket.readBuf32Long()
|
||||||
|
// readPacket.discardExact(1)
|
||||||
|
//
|
||||||
|
// readPacket.readBytes(readPacket.readShort().toInt()).also {
|
||||||
|
// readPacket.release()
|
||||||
|
// }.decodeProtobuf<GroupCommonTipsEvent>()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// val targetId = detail[1, 13, 2].asUtf8String
|
||||||
|
// val newCardList = detail[1, 13, 3].asList
|
||||||
|
// var newCard = ""
|
||||||
|
// newCardList
|
||||||
|
// .value
|
||||||
|
// .forEach {
|
||||||
|
// if (it[1].asInt == 1) {
|
||||||
|
// newCard = it[2].asUtf8String
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// val groupId = detail[1, 13, 4].asLong
|
||||||
|
// var oldCard = ""
|
||||||
|
// val targetQQ = ContactHelper.getUinByUidAsync(targetId).toLong()
|
||||||
|
// LogCenter.log("群组[$groupId]成员$targetQQ 群名片变动 -> $newCard")
|
||||||
|
// // oldCard暂时获取不到
|
||||||
|
// if (!GlobalEventTransmitter.GroupNoticeTransmitter
|
||||||
|
// .transCardChange(msgTime, targetQQ, oldCard, newCard, groupId)
|
||||||
|
// ) {
|
||||||
|
// LogCenter.log("群名片变动推送失败!", Level.WARN)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun onGroupUniqueTitleChange(msgTime: Long, body: MsgBody) {
|
||||||
|
val event = runCatching {
|
||||||
|
body.msgContent!!.decodeProtobuf<GroupCommonTipsEvent>()
|
||||||
|
}.getOrElse {
|
||||||
|
val readPacket = ByteReadPacket(body.msgContent!!)
|
||||||
|
readPacket.readBuf32Long()
|
||||||
|
readPacket.discardExact(1)
|
||||||
|
|
||||||
|
readPacket.readBytes(readPacket.readShort().toInt()).also {
|
||||||
|
readPacket.release()
|
||||||
|
}.decodeProtobuf<GroupCommonTipsEvent>()
|
||||||
|
}
|
||||||
|
val groupId = event.groupCode.toLong()
|
||||||
|
val detail = event.uniqueTitleChangeDetail!!.first()
|
||||||
|
|
||||||
|
//detail = if (detail[5] is ProtoList) {
|
||||||
|
// (detail[5] as ProtoList).value[0]
|
||||||
|
//} else {
|
||||||
|
// detail[5]
|
||||||
|
// }
|
||||||
|
|
||||||
|
val targetUin = detail.targetUin.toLong()
|
||||||
|
|
||||||
|
// 恭喜<{\"cmd\":5,\"data\":\"qq\",\"text}\":\"nickname\"}>获得群主授予的<{\"cmd\":1,\"data\":\"https://qun.qq.com/qqweb/m/qun/medal/detail.html?_wv=16777223&bid=2504&gc=gid&isnew=1&medal=302&uin=uin\",\"text\":\"title\",\"url\":\"https://qun.qq.com/qqweb/m/qun/medal/detail.html?_wv=16777223&bid=2504&gc=gid&isnew=1&medal=302&uin=uin\"}>头衔
|
||||||
|
val titleChangeInfo = detail.wording
|
||||||
|
if (titleChangeInfo.indexOf("群主授予") == -1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val titleJson = titleChangeInfo.split("获得群主授予的<")[1].replace(">头衔", "")
|
||||||
|
val titleJsonObj = Json.decodeFromString<JsonElement>(titleJson).asJsonObject
|
||||||
|
val title = titleJsonObj["text"].asString
|
||||||
|
|
||||||
|
LogCenter.log("群组[$groupId]成员$targetUin 获得群头衔 -> $title")
|
||||||
|
|
||||||
|
if (!GlobalEventTransmitter.GroupNoticeTransmitter
|
||||||
|
.transTitleChange(msgTime, targetUin, title, groupId)
|
||||||
|
) {
|
||||||
|
LogCenter.log("群头衔变动推送失败!", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun onEssenceMessage(
|
||||||
|
msgTime: Long,
|
||||||
|
clientInfo: MessagePushClientInfo?,
|
||||||
|
body: MsgBody
|
||||||
|
) {
|
||||||
|
if (clientInfo == null) return
|
||||||
|
val event = runCatching {
|
||||||
|
body.msgContent!!.decodeProtobuf<GroupCommonTipsEvent>()
|
||||||
|
}.getOrElse {
|
||||||
|
val readPacket = ByteReadPacket(body.msgContent!!)
|
||||||
|
readPacket.readBuf32Long()
|
||||||
|
readPacket.discardExact(1)
|
||||||
|
|
||||||
|
readPacket.readBytes(readPacket.readShort().toInt()).also {
|
||||||
|
readPacket.release()
|
||||||
|
}.decodeProtobuf<GroupCommonTipsEvent>()
|
||||||
|
}
|
||||||
|
val groupId = event.groupCode.toLong()
|
||||||
|
val detail = event.essenceMsgInfo!!.first()
|
||||||
|
|
||||||
|
val msgSeq = event.msgSeq.toLong()
|
||||||
|
val senderUin = detail.sender.toLong()
|
||||||
|
val operatorUin = detail.operator.toLong()
|
||||||
|
|
||||||
|
when (val type = detail.type) {
|
||||||
|
1u -> {
|
||||||
|
LogCenter.log("群设精消息(groupId=$groupId, sender=$senderUin, msgSeq=$msgSeq, operator=$operatorUin)")
|
||||||
|
}
|
||||||
|
2u -> {
|
||||||
|
LogCenter.log("群撤精消息(groupId=$groupId, sender=$senderUin, msgId=$msgSeq, operator=$operatorUin)")
|
||||||
|
}
|
||||||
|
else -> error("onEssenceMessage unknown type: $type")
|
||||||
|
}
|
||||||
|
|
||||||
|
val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, groupId.toString())
|
||||||
|
val sourceRecord = withTimeoutOrNull(3000) {
|
||||||
|
suspendCancellableCoroutine {
|
||||||
|
QRoute.api(IMsgService::class.java).getMsgsBySeqAndCount(contact, msgSeq, 1, true) { _, _, records ->
|
||||||
|
it.resume(records)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}?.firstOrNull()
|
||||||
|
|
||||||
|
if (sourceRecord == null) {
|
||||||
|
LogCenter.log("无法获取源消息记录,无法推送精华消息变动!", Level.WARN)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val msgId = sourceRecord.msgId
|
||||||
|
if (!GlobalEventTransmitter.GroupNoticeTransmitter
|
||||||
|
.transEssenceChange(msgTime, senderUin, operatorUin, msgId, groupId, detail.type)
|
||||||
|
) {
|
||||||
|
LogCenter.log("精华消息变动推送失败!", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private suspend fun onGroupCommonTips(time: Long, body: MsgBody) {
|
||||||
|
val event = runCatching {
|
||||||
|
body.msgContent!!.decodeProtobuf<GroupCommonTipsEvent>()
|
||||||
|
}.getOrElse {
|
||||||
|
val readPacket = ByteReadPacket(body.msgContent!!)
|
||||||
|
readPacket.discardExact(4)
|
||||||
|
readPacket.discardExact(1)
|
||||||
|
|
||||||
|
readPacket.readBytes(readPacket.readShort().toInt()).also {
|
||||||
|
readPacket.release()
|
||||||
|
}.decodeProtobuf<GroupCommonTipsEvent>()
|
||||||
|
}
|
||||||
|
val groupId = event.groupCode.toLong()
|
||||||
|
val detail = event.baseTips!!.first()
|
||||||
|
|
||||||
|
val params = detail.params!!.associate {
|
||||||
|
it.key to it.value
|
||||||
|
}
|
||||||
|
|
||||||
|
val target = params["uin_str2"] ?: params["mqq_uin"] ?: return
|
||||||
|
val operation = params["uin_str1"] ?: return
|
||||||
|
val suffix = params["suffix_str"] ?: ""
|
||||||
|
val actionImg = params["action_img_url"] ?: ""
|
||||||
|
val action = params["alt_str1"]
|
||||||
|
?: params["action_str"]
|
||||||
|
?: params["user_sign"]
|
||||||
|
?: ""
|
||||||
|
val rankImg = params["rank_img"] ?: ""
|
||||||
|
|
||||||
|
when (detail.type) {
|
||||||
|
1061u -> {
|
||||||
|
LogCenter.log("群戳一戳($groupId): $operation $action $target $suffix")
|
||||||
|
if (!GlobalEventTransmitter.GroupNoticeTransmitter
|
||||||
|
.transGroupPoke(time, operation.toLong(), target.toLong(), action, suffix, actionImg, groupId)
|
||||||
|
) {
|
||||||
|
LogCenter.log("群戳一戳推送失败!", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
1068u -> {
|
||||||
|
LogCenter.log("群打卡($groupId): $action $target")
|
||||||
|
if (!GlobalEventTransmitter.GroupNoticeTransmitter
|
||||||
|
.transGroupSign(time, target.toLong(), action, rankImg, groupId)
|
||||||
|
) {
|
||||||
|
LogCenter.log("群打卡推送失败!", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
LogCenter.log("onGroupPokeAndGroupSign unknown type ${detail.type}", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun onC2CRecall(time: Long, body: MsgBody) {
|
||||||
|
val event = body.msgContent!!.decodeProtobuf<C2CRecallEvent>()
|
||||||
|
val head = event.head!!
|
||||||
|
|
||||||
|
val operationUid = head.operator!!
|
||||||
|
val operator = ContactHelper.getUinByUidAsync(operationUid).toLong()
|
||||||
|
|
||||||
|
val msgSeq = head.msgSeq
|
||||||
|
val tipText = head.wording?.wording ?: ""
|
||||||
|
|
||||||
|
val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, operationUid)
|
||||||
|
val sourceRecord = withTimeoutOrNull(3000) {
|
||||||
|
suspendCancellableCoroutine {
|
||||||
|
QRoute.api(IMsgService::class.java).getMsgsBySeqAndCount(contact, msgSeq, 1, true) { _, _, records ->
|
||||||
|
it.resume(records)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}?.firstOrNull()
|
||||||
|
|
||||||
|
if (sourceRecord == null) {
|
||||||
|
LogCenter.log("无法获取源消息记录,无法推送撤回消息!", Level.WARN)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val msgId = sourceRecord.msgId
|
||||||
|
|
||||||
|
LogCenter.log("私聊消息撤回: $operator, seq = $msgSeq, msgId = ${msgId}, tip = $tipText")
|
||||||
|
|
||||||
|
if (!GlobalEventTransmitter.PrivateNoticeTransmitter
|
||||||
|
.transPrivateRecall(time, operator, msgId, tipText)
|
||||||
|
) {
|
||||||
|
LogCenter.log("私聊消息撤回推送失败!", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun onGroupMemIncreased(time: Long, body: MsgBody) {
|
||||||
|
val event = body.msgContent!!.decodeProtobuf<GroupListChangeEvent>()
|
||||||
|
val groupCode = event.groupCode
|
||||||
|
val targetUid = event.memberUid
|
||||||
|
val type = event.type
|
||||||
|
|
||||||
|
GroupHelper.getGroupMemberList(groupCode.toString(), true).onFailure {
|
||||||
|
LogCenter.log("新成员加入刷新群成员列表失败: $groupCode", Level.WARN)
|
||||||
|
}.onSuccess {
|
||||||
|
LogCenter.log("新成员加入刷新群成员列表成功,群成员数量: ${it.size}", Level.INFO)
|
||||||
|
}
|
||||||
|
|
||||||
|
val operatorUid = event.operatorUid
|
||||||
|
val operator = ContactHelper.getUinByUidAsync(operatorUid).toLong()
|
||||||
|
val target = ContactHelper.getUinByUidAsync(targetUid).toLong()
|
||||||
|
LogCenter.log("群成员增加($groupCode): $target, type = $type")
|
||||||
|
|
||||||
|
if (!GlobalEventTransmitter.GroupNoticeTransmitter
|
||||||
|
.transGroupMemberNumIncreased(
|
||||||
|
time,
|
||||||
|
target,
|
||||||
|
targetUid,
|
||||||
|
groupCode,
|
||||||
|
operator,
|
||||||
|
operatorUid,
|
||||||
|
when (type) {
|
||||||
|
130 -> GroupMemberIncreasedType.APPROVE
|
||||||
|
131 -> GroupMemberIncreasedType.INVITE
|
||||||
|
else -> GroupMemberIncreasedType.APPROVE
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
LogCenter.log("群成员增加推送失败!", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun onGroupMemberDecreased(time: Long, body: MsgBody) {
|
||||||
|
val event = body.msgContent!!.decodeProtobuf<GroupListChangeEvent>()
|
||||||
|
val groupCode = event.groupCode
|
||||||
|
val targetUid = event.memberUid
|
||||||
|
val type = event.type
|
||||||
|
val operatorUid = event.operatorUid
|
||||||
|
|
||||||
|
GroupHelper.getGroupMemberList(groupCode.toString(), true).onFailure {
|
||||||
|
LogCenter.log("新成员加入刷新群成员列表失败: $groupCode", Level.WARN)
|
||||||
|
}.onSuccess {
|
||||||
|
LogCenter.log("新成员加入刷新群成员列表成功,群成员数量: ${it.size}", Level.INFO)
|
||||||
|
}
|
||||||
|
|
||||||
|
val operator = ContactHelper.getUinByUidAsync(operatorUid).toLong()
|
||||||
|
val target = ContactHelper.getUinByUidAsync(targetUid).toLong()
|
||||||
|
val subtype = when (type) {
|
||||||
|
130 -> GroupMemberDecreasedType.LEAVE
|
||||||
|
131 -> GroupMemberDecreasedType.KICK
|
||||||
|
3 -> GroupMemberDecreasedType.KICK_ME
|
||||||
|
else -> GroupMemberDecreasedType.KICK
|
||||||
|
}
|
||||||
|
|
||||||
|
LogCenter.log("群成员减少($groupCode): $target, type = $subtype ($type)")
|
||||||
|
|
||||||
|
if (!GlobalEventTransmitter.GroupNoticeTransmitter
|
||||||
|
.transGroupMemberNumDecreased(
|
||||||
|
time,
|
||||||
|
target,
|
||||||
|
targetUid,
|
||||||
|
groupCode,
|
||||||
|
operator,
|
||||||
|
operatorUid,
|
||||||
|
subtype
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
LogCenter.log("群成员减少推送失败!", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun onGroupAdminChange(msgTime: Long, body: MsgBody) {
|
||||||
|
val event = body.msgContent!!.decodeProtobuf<GroupAdminChangeEvent>()
|
||||||
|
val groupCode = event.groupCode
|
||||||
|
if (event.operation == null) return
|
||||||
|
val operation = event.operation!!
|
||||||
|
if (operation.setInfo == null && operation.unsetInfo == null) return
|
||||||
|
|
||||||
|
val isSetAdmin: Boolean
|
||||||
|
val targetUid: String
|
||||||
|
if (operation.setInfo == null) {
|
||||||
|
isSetAdmin = false
|
||||||
|
targetUid = operation.unsetInfo!!.targetUid!!
|
||||||
|
} else {
|
||||||
|
isSetAdmin = true
|
||||||
|
targetUid = operation.setInfo!!.targetUid!!
|
||||||
|
}
|
||||||
|
|
||||||
|
val target = ContactHelper.getUinByUidAsync(targetUid).toLong()
|
||||||
|
LogCenter.log("群管理员变动($groupCode): $target, isSetAdmin = $isSetAdmin")
|
||||||
|
|
||||||
|
if (!GlobalEventTransmitter.GroupNoticeTransmitter
|
||||||
|
.transGroupAdminChanged(msgTime, target, targetUid, groupCode, isSetAdmin)
|
||||||
|
) {
|
||||||
|
LogCenter.log("群管理员变动推送失败!", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun onGroupBan(msgTime: Long, body: MsgBody) {
|
||||||
|
val event = body.msgContent!!.decodeProtobuf<GroupBanEvent>()
|
||||||
|
val groupCode = event.groupCode.toLong()
|
||||||
|
val operatorUid = event.operatorUid
|
||||||
|
val wholeBan = event.target?.target?.targetUid == null
|
||||||
|
val targetUid = event.target?.target?.targetUid ?: ""
|
||||||
|
val rawDuration = event.target?.target?.rawDuration?.toInt() ?: 0
|
||||||
|
val operator = ContactHelper.getUinByUidAsync(operatorUid).toLong()
|
||||||
|
val duration = if (wholeBan) -1 else rawDuration
|
||||||
|
val target = if (wholeBan) 0 else ContactHelper.getUinByUidAsync(targetUid).toLong()
|
||||||
|
|
||||||
|
if (wholeBan) {
|
||||||
|
LogCenter.log("群全员禁言($groupCode): $operator -> ${if (rawDuration != 0) "开启" else "关闭"}")
|
||||||
|
if (!GlobalEventTransmitter.GroupNoticeTransmitter
|
||||||
|
.transGroupWholeBan(msgTime, groupCode, operator, rawDuration != 0)
|
||||||
|
) {
|
||||||
|
LogCenter.log("群禁言推送失败!", Level.WARN)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LogCenter.log("群禁言($groupCode): $operator -> $target, 时长 = ${duration}s")
|
||||||
|
if (!GlobalEventTransmitter.GroupNoticeTransmitter
|
||||||
|
.transGroupBan(msgTime, operator, operatorUid, target, targetUid, groupCode, duration)
|
||||||
|
) {
|
||||||
|
LogCenter.log("群禁言推送失败!", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun onGroupRecall(time: Long, body: MsgBody) {
|
||||||
|
val event = runCatching {
|
||||||
|
body.msgContent!!.decodeProtobuf<GroupCommonTipsEvent>()
|
||||||
|
}.getOrElse {
|
||||||
|
val readPacket = ByteReadPacket(body.msgContent!!)
|
||||||
|
readPacket.discardExact(4)
|
||||||
|
readPacket.discardExact(1)
|
||||||
|
readPacket.readBytes(readPacket.readShort().toInt()).also {
|
||||||
|
readPacket.release()
|
||||||
|
}.decodeProtobuf<GroupCommonTipsEvent>()
|
||||||
|
}
|
||||||
|
val groupCode = event.groupCode.toLong()
|
||||||
|
val detail = event.recallDetails!!
|
||||||
|
val operatorUid = detail.operatorUid
|
||||||
|
val targetUid = detail.msgInfo!!.senderUid
|
||||||
|
val msgSeq = detail.msgInfo!!.msgSeq.toLong()
|
||||||
|
val tipText = detail.wording?.wording ?: ""
|
||||||
|
|
||||||
|
val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, groupCode.toString())
|
||||||
|
val sourceRecord = withTimeoutOrNull(3000) {
|
||||||
|
suspendCancellableCoroutine {
|
||||||
|
QRoute.api(IMsgService::class.java).getMsgsBySeqAndCount(contact, msgSeq, 1, true) { _, _, records ->
|
||||||
|
it.resume(records)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}?.firstOrNull()
|
||||||
|
|
||||||
|
if (sourceRecord == null) {
|
||||||
|
LogCenter.log("无法获取源消息记录,无法推送撤回消息!", Level.WARN)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val msgId = sourceRecord.msgId
|
||||||
|
|
||||||
|
val operator = ContactHelper.getUinByUidAsync(operatorUid).toLong()
|
||||||
|
val target = ContactHelper.getUinByUidAsync(targetUid).toLong()
|
||||||
|
LogCenter.log("群消息撤回($groupCode): $operator -> $target, seq = $msgSeq, id = $msgId, tip = $tipText")
|
||||||
|
|
||||||
|
if (!GlobalEventTransmitter.GroupNoticeTransmitter
|
||||||
|
.transGroupMsgRecall(time, operator, operatorUid, target, targetUid, groupCode, msgId, tipText)
|
||||||
|
) {
|
||||||
|
LogCenter.log("群消息撤回推送失败!", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun onGroupApply(time: Long, contentHead: ContentHead, body: MsgBody) {
|
||||||
|
when (contentHead.msgType) {
|
||||||
|
84 -> {
|
||||||
|
val event = body.msgContent!!.decodeProtobuf<GroupApplyEvent>()
|
||||||
|
val groupCode = event.groupCode
|
||||||
|
val applierUid = event.applierUid
|
||||||
|
val reason = event.applyMsg ?: ""
|
||||||
|
var applier = ContactHelper.getUinByUidAsync(applierUid).toLong()
|
||||||
|
if (applier == QQInterfaces.app.longAccountUin) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val msgSeq = contentHead.msgSeq
|
||||||
|
val flag = try {
|
||||||
|
var reqs = requestGroupSystemMsgNew(10, 1)
|
||||||
|
val riskReqs = requestGroupSystemMsgNew(5, 2)
|
||||||
|
reqs = reqs + riskReqs
|
||||||
|
val req = reqs.firstOrNull {
|
||||||
|
it.msg_time.get() == time && it.msg?.group_code?.get() == groupCode
|
||||||
|
}
|
||||||
|
val seq = req?.msg_seq?.get() ?: time
|
||||||
|
if (applier == 0L) {
|
||||||
|
applier = req?.req_uin?.get() ?: 0L
|
||||||
|
}
|
||||||
|
"$seq;$groupCode;$applier"
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
"$time;$groupCode;$applier"
|
||||||
|
}
|
||||||
|
LogCenter.log("入群申请($groupCode) $applier: \"$reason\", seq: $msgSeq")
|
||||||
|
if (!GlobalEventTransmitter.RequestTransmitter
|
||||||
|
.transGroupApply(time, applier, applierUid, reason, groupCode, flag, GroupApplyType.GROUP_APPLY_ADD)
|
||||||
|
) {
|
||||||
|
LogCenter.log("入群申请推送失败!", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
528 -> {
|
||||||
|
val event = body.msgContent!!.decodeProtobuf<GroupInvitedApplyEvent>()
|
||||||
|
val groupCode = event.applyInfo?.groupCode ?: return
|
||||||
|
val applierUid = event.applyInfo?.applierUid ?: return
|
||||||
|
var applier = ContactHelper.getUinByUidAsync(applierUid).toLong()
|
||||||
|
if (applier == QQInterfaces.app.longAccountUin) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ((event.applyInfo?.type ?: return) < 3) {
|
||||||
|
// todo
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val flag = try {
|
||||||
|
var reqs = requestGroupSystemMsgNew(10, 1)
|
||||||
|
val riskReqs = requestGroupSystemMsgNew(5, 2)
|
||||||
|
reqs = reqs + riskReqs
|
||||||
|
val req = reqs.firstOrNull() {
|
||||||
|
it.msg_time.get() == time
|
||||||
|
}
|
||||||
|
val seq = req?.msg_seq?.get() ?: time
|
||||||
|
if (applier == 0L) {
|
||||||
|
applier = req?.req_uin?.get() ?: 0L
|
||||||
|
}
|
||||||
|
"$seq;$groupCode;$applier"
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
"$time;$groupCode;$applier"
|
||||||
|
}
|
||||||
|
LogCenter.log("邀请入群申请($groupCode): $applier")
|
||||||
|
if (!GlobalEventTransmitter.RequestTransmitter
|
||||||
|
.transGroupApply(time, applier, applierUid, "", groupCode, flag, GroupApplyType.GROUP_APPLY_ADD)
|
||||||
|
) {
|
||||||
|
LogCenter.log("邀请入群申请推送失败!", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun onInviteGroup(time: Long, msgHead: ResponseHead, body: MsgBody) {
|
||||||
|
val event = body.msgContent!!.decodeProtobuf<GroupInviteEvent>()
|
||||||
|
val groupCode = event.groupCode
|
||||||
|
val invitorUid = event.inviterUid
|
||||||
|
val invitor = ContactHelper.getUinByUidAsync(invitorUid).toLong()
|
||||||
|
val uin = msgHead.receiver
|
||||||
|
LogCenter.log("邀请入群: $groupCode, 邀请者: \"$invitor\"")
|
||||||
|
val flag = try {
|
||||||
|
var reqs = requestGroupSystemMsgNew(10, 1)
|
||||||
|
val riskReqs = requestGroupSystemMsgNew(10, 2)
|
||||||
|
reqs = reqs + riskReqs
|
||||||
|
val req = reqs.firstOrNull {
|
||||||
|
it.msg_time.get() == time
|
||||||
|
}
|
||||||
|
val seq = req?.msg_seq?.get() ?: time
|
||||||
|
"$seq;$groupCode;$uin"
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
"$time;$groupCode;$uin"
|
||||||
|
}
|
||||||
|
if (!GlobalEventTransmitter.RequestTransmitter
|
||||||
|
.transGroupApply(time, invitor, invitorUid, "", groupCode, flag, GroupApplyType.GROUP_APPLY_INVITE)
|
||||||
|
) {
|
||||||
|
LogCenter.log("邀请入群推送失败!", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,316 @@
|
|||||||
|
package qq.service.kernel
|
||||||
|
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.BroadcastHelperTransNotifyInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.Contact
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.ContactMsgBoxInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.CustomWithdrawConfig
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.DevInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.DownloadRelateEmojiResultInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.EmojiNotifyInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.EmojiResourceInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.FileTransNotifyInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.FirstViewDirectMsgNotifyInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.FirstViewGroupGuildInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.FreqLimitInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.GroupFileListResult
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.GroupGuildNotifyInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.GroupItem
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.GuildInteractiveNotificationItem
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.GuildMsgAbFlag
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.GuildNotificationAbstractInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.HitRelatedEmojiWordsResult
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.IKernelMsgListener
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.ImportOldDbMsgNotifyInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.InputStatusInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.KickedInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgAbstract
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgSetting
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.RecvdOrder
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.RelatedWordEmojiInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.SearchGroupFileResult
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.TabStatusInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.TempChatInfo
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.UnreadCntInfo
|
||||||
|
import java.util.ArrayList
|
||||||
|
import java.util.HashMap
|
||||||
|
|
||||||
|
abstract class SimpleKernelMsgListener: IKernelMsgListener {
|
||||||
|
override fun onAddSendMsg(msgRecord: MsgRecord?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBroadcastHelperDownloadComplete(broadcastHelperTransNotifyInfo: BroadcastHelperTransNotifyInfo?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBroadcastHelperProgerssUpdate(broadcastHelperTransNotifyInfo: BroadcastHelperTransNotifyInfo?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onChannelFreqLimitInfoUpdate(
|
||||||
|
contact: Contact?,
|
||||||
|
z: Boolean,
|
||||||
|
freqLimitInfo: FreqLimitInfo?
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onContactUnreadCntUpdate(hashMap: HashMap<Int, HashMap<String, UnreadCntInfo>>?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCustomWithdrawConfigUpdate(customWithdrawConfig: CustomWithdrawConfig?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDraftUpdate(contact: Contact?, arrayList: ArrayList<MsgElement>?, j2: Long) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEmojiDownloadComplete(emojiNotifyInfo: EmojiNotifyInfo?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEmojiResourceUpdate(emojiResourceInfo: EmojiResourceInfo?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFeedEventUpdate(firstViewDirectMsgNotifyInfo: FirstViewDirectMsgNotifyInfo?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFileMsgCome(arrayList: ArrayList<MsgRecord>?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFirstViewDirectMsgUpdate(firstViewDirectMsgNotifyInfo: FirstViewDirectMsgNotifyInfo?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFirstViewGroupGuildMapping(arrayList: ArrayList<FirstViewGroupGuildInfo>?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGrabPasswordRedBag(
|
||||||
|
i2: Int,
|
||||||
|
str: String?,
|
||||||
|
i3: Int,
|
||||||
|
recvdOrder: RecvdOrder?,
|
||||||
|
msgRecord: MsgRecord?
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGroupFileInfoAdd(groupItem: GroupItem?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGroupFileInfoUpdate(groupFileListResult: GroupFileListResult?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGroupGuildUpdate(groupGuildNotifyInfo: GroupGuildNotifyInfo?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGroupTransferInfoAdd(groupItem: GroupItem?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGroupTransferInfoUpdate(groupFileListResult: GroupFileListResult?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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 onKickedOffLine(kickedInfo: KickedInfo?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLineDev(arrayList: ArrayList<DevInfo>?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLogLevelChanged(j2: Long) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMsgAbstractUpdate(arrayList: ArrayList<MsgAbstract>?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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 onMsgInfoListUpdate(arrayList: ArrayList<MsgRecord>?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMsgQRCodeStatusChanged(i2: Int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMsgRecall(chatType: Int, tips: String?, msgId: Long) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMsgSecurityNotify(msgRecord: MsgRecord?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMsgSettingUpdate(msgSetting: MsgSetting?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNtFirstViewMsgSyncEnd() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNtMsgSyncEnd() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNtMsgSyncStart() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReadFeedEventUpdate(firstViewDirectMsgNotifyInfo: FirstViewDirectMsgNotifyInfo?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRecvGroupGuildFlag(i2: Int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRecvMsg(records: ArrayList<MsgRecord>) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRecvMsgSvrRspTransInfo(
|
||||||
|
j2: Long,
|
||||||
|
contact: Contact?,
|
||||||
|
i2: Int,
|
||||||
|
i3: Int,
|
||||||
|
str: String?,
|
||||||
|
bArr: ByteArray?
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRecvOnlineFileMsg(arrayList: ArrayList<MsgRecord>?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRecvS2CMsg(arrayList: ArrayList<Byte>?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRecvSysMsg(arrayList: ArrayList<Byte>?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRecvUDCFlag(i2: Int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRichMediaDownloadComplete(fileTransNotifyInfo: FileTransNotifyInfo?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRichMediaProgerssUpdate(fileTransNotifyInfo: FileTransNotifyInfo?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRichMediaUploadComplete(fileTransNotifyInfo: FileTransNotifyInfo) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSearchGroupFileInfoUpdate(searchGroupFileResult: SearchGroupFileResult?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSendMsgError(j2: Long, contact: Contact?, i2: Int, str: String?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSysMsgNotification(i2: Int, j2: Long, j3: Long, arrayList: ArrayList<Byte>?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTempChatInfoUpdate(tempChatInfo: TempChatInfo?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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>?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onlineStatusBigIconDownloadPush(i2: Int, j2: Long, str: String?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onlineStatusSmallIconDownloadPush(i2: Int, j2: Long, str: String?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,16 @@
|
|||||||
package qq.service.msg
|
package qq.service.msg
|
||||||
|
|
||||||
import com.tencent.qqnt.kernel.api.IKernelService
|
import com.tencent.qqnt.kernel.api.IKernelService
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.Contact
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.TempChatInfo
|
import com.tencent.qqnt.kernel.nativeinterface.TempChatInfo
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
import moe.fuqiuluo.shamrock.helper.Level
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
import qq.service.QQInterfaces
|
import qq.service.QQInterfaces
|
||||||
|
import qq.service.contact.ContactHelper
|
||||||
import qq.service.internals.msgService
|
import qq.service.internals.msgService
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
@ -28,4 +32,41 @@ internal object MessageHelper: QQInterfaces() {
|
|||||||
} ?: return Result.failure(Exception("获取临时会话信息失败"))
|
} ?: return Result.failure(Exception("获取临时会话信息失败"))
|
||||||
return Result.success(info)
|
return Result.success(info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun generateContact(record: MsgRecord): Contact {
|
||||||
|
val peerId = when (record.chatType) {
|
||||||
|
MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> record.senderUid
|
||||||
|
MsgConstant.KCHATTYPEGUILD -> record.channelId
|
||||||
|
else -> record.peerUin.toString()
|
||||||
|
}
|
||||||
|
return Contact(record.chatType, peerId, if (record.chatType == MsgConstant.KCHATTYPEGUILD) {
|
||||||
|
record.guildId
|
||||||
|
} else if(record.chatType == MsgConstant.KCHATTYPETEMPC2CFROMGROUP) {
|
||||||
|
val tempInfo = getTempChatInfo(record.chatType, peerId).getOrThrow()
|
||||||
|
tempInfo.groupCode
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun generateContact(chatType: Int, id: String, subId: String = ""): Contact {
|
||||||
|
val peerId = when (chatType) {
|
||||||
|
MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> {
|
||||||
|
if (id.startsWith("u_")) id
|
||||||
|
else ContactHelper.getUidByUinAsync(id.toLong())
|
||||||
|
}
|
||||||
|
else -> id
|
||||||
|
}
|
||||||
|
return if (chatType == MsgConstant.KCHATTYPEGUILD) {
|
||||||
|
Contact(chatType, subId, peerId)
|
||||||
|
} else {
|
||||||
|
Contact(chatType, peerId, subId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateMsgId(chatType: Int): Long {
|
||||||
|
return createMessageUniseq(chatType, System.currentTimeMillis())
|
||||||
|
}
|
||||||
|
|
||||||
|
external fun createMessageUniseq(chatType: Int, time: Long): Long
|
||||||
}
|
}
|
425
xposed/src/main/java/qq/service/msg/MsgConvertor.kt
Normal file
425
xposed/src/main/java/qq/service/msg/MsgConvertor.kt
Normal file
@ -0,0 +1,425 @@
|
|||||||
|
package qq.service.msg
|
||||||
|
|
||||||
|
import com.tencent.mobileqq.qroute.QRoute
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
|
||||||
|
import com.tencent.qqnt.msg.api.IMsgService
|
||||||
|
import io.kritor.event.AtElement
|
||||||
|
import io.kritor.event.Element
|
||||||
|
import io.kritor.event.ElementKt
|
||||||
|
import io.kritor.event.ImageType
|
||||||
|
import io.kritor.event.Scene
|
||||||
|
import io.kritor.event.atElement
|
||||||
|
import io.kritor.event.basketballElement
|
||||||
|
import io.kritor.event.buttonAction
|
||||||
|
import io.kritor.event.buttonActionPermission
|
||||||
|
import io.kritor.event.buttonRender
|
||||||
|
import io.kritor.event.contactElement
|
||||||
|
import io.kritor.event.diceElement
|
||||||
|
import io.kritor.event.faceElement
|
||||||
|
import io.kritor.event.forwardElement
|
||||||
|
import io.kritor.event.imageElement
|
||||||
|
import io.kritor.event.jsonElement
|
||||||
|
import io.kritor.event.locationElement
|
||||||
|
import io.kritor.event.pokeElement
|
||||||
|
import io.kritor.event.replyElement
|
||||||
|
import io.kritor.event.rpsElement
|
||||||
|
import io.kritor.event.textElement
|
||||||
|
import io.kritor.event.videoElement
|
||||||
|
import io.kritor.event.voiceElement
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
|
import moe.fuqiuluo.shamrock.helper.ActionMsgException
|
||||||
|
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.tools.asJsonArray
|
||||||
|
import moe.fuqiuluo.shamrock.tools.asJsonObject
|
||||||
|
import moe.fuqiuluo.shamrock.tools.asString
|
||||||
|
import moe.fuqiuluo.shamrock.tools.hex2ByteArray
|
||||||
|
import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
|
||||||
|
import moe.fuqiuluo.shamrock.tools.toHexString
|
||||||
|
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.contact.ContactHelper
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
|
typealias NtMessages = ArrayList<MsgElement>
|
||||||
|
typealias Convertor = suspend (MsgRecord, MsgElement) -> Result<Element>
|
||||||
|
|
||||||
|
suspend fun NtMessages.toKritorMessages(record: MsgRecord): ArrayList<Element> {
|
||||||
|
val result = arrayListOf<Element>()
|
||||||
|
forEach {
|
||||||
|
MsgConvertor[it.elementType]?.invoke(record, it)?.onSuccess {
|
||||||
|
result.add(it)
|
||||||
|
}?.onFailure {
|
||||||
|
if (it !is ActionMsgException) {
|
||||||
|
LogCenter.log("消息转换异常: " + it.stackTraceToString(), Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private object MsgConvertor {
|
||||||
|
private val convertorMap = hashMapOf(
|
||||||
|
MsgConstant.KELEMTYPETEXT to ::convertText,
|
||||||
|
MsgConstant.KELEMTYPEFACE to ::convertFace,
|
||||||
|
MsgConstant.KELEMTYPEPIC to ::convertImage,
|
||||||
|
MsgConstant.KELEMTYPEPTT to ::convertVoice,
|
||||||
|
MsgConstant.KELEMTYPEVIDEO to ::convertVideo,
|
||||||
|
MsgConstant.KELEMTYPEMARKETFACE to ::convertMarketFace,
|
||||||
|
MsgConstant.KELEMTYPEARKSTRUCT to ::convertStructJson,
|
||||||
|
MsgConstant.KELEMTYPEREPLY to ::convertReply,
|
||||||
|
//MsgConstant.KELEMTYPEGRAYTIP to ::convertGrayTips,
|
||||||
|
MsgConstant.KELEMTYPEFILE to ::convertFile,
|
||||||
|
MsgConstant.KELEMTYPEMARKDOWN to ::convertMarkdown,
|
||||||
|
//MsgConstant.KELEMTYPEMULTIFORWARD to MsgElementConverter::convertXmlMultiMsgElem,
|
||||||
|
//MsgConstant.KELEMTYPESTRUCTLONGMSG to MsgElementConverter::convertXmlLongMsgElem,
|
||||||
|
MsgConstant.KELEMTYPEFACEBUBBLE to ::convertBubbleFace,
|
||||||
|
MsgConstant.KELEMTYPEINLINEKEYBOARD to ::convertInlineKeyboard
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun convertText(record: MsgRecord, element: MsgElement): Result<Element> {
|
||||||
|
val text = element.textElement
|
||||||
|
val elem = Element.newBuilder()
|
||||||
|
if (text.atType != MsgConstant.ATTYPEUNKNOWN) {
|
||||||
|
elem.setAt(atElement {
|
||||||
|
this.uid = text.atNtUid
|
||||||
|
this.uin = ContactHelper.getUinByUidAsync(text.atNtUid).toLong()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
elem.setText(textElement {
|
||||||
|
this.text = text.content
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return Result.success(elem.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun convertFace(record: MsgRecord, element: MsgElement): Result<Element> {
|
||||||
|
val face = element.faceElement
|
||||||
|
val elem = Element.newBuilder()
|
||||||
|
if (face.faceType == 5) {
|
||||||
|
elem.setPoke(pokeElement {
|
||||||
|
this.id = face.vaspokeId
|
||||||
|
this.type = face.pokeType
|
||||||
|
this.strength = face.pokeStrength
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
when(face.faceIndex) {
|
||||||
|
114 -> elem.setBasketball(basketballElement {
|
||||||
|
this.id = face.resultId.ifNullOrEmpty { "0" }?.toInt() ?: 0
|
||||||
|
})
|
||||||
|
358 -> elem.setDice(diceElement {
|
||||||
|
this.id = face.resultId.ifNullOrEmpty { "0" }?.toInt() ?: 0
|
||||||
|
})
|
||||||
|
359 -> elem.setRps(rpsElement {
|
||||||
|
this.id = face.resultId.ifNullOrEmpty { "0" }?.toInt() ?: 0
|
||||||
|
})
|
||||||
|
394 -> elem.setFace(faceElement {
|
||||||
|
this.id = face.faceIndex
|
||||||
|
this.isBig = face.faceType == 3
|
||||||
|
this.result = face.resultId.ifNullOrEmpty { "1" }?.toInt() ?: 1
|
||||||
|
})
|
||||||
|
else -> elem.setFace(faceElement {
|
||||||
|
this.id = face.faceIndex
|
||||||
|
this.isBig = face.faceType == 3
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Result.success(elem.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun convertImage(record: MsgRecord, element: MsgElement): Result<Element> {
|
||||||
|
val image = element.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
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageDB.getInstance().imageMappingDao().insert(
|
||||||
|
ImageMapping(
|
||||||
|
fileName = md5,
|
||||||
|
md5 = md5,
|
||||||
|
chatType = record.chatType,
|
||||||
|
size = image.fileSize,
|
||||||
|
sha = "",
|
||||||
|
fileId = image.fileUuid,
|
||||||
|
storeId = storeId,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val originalUrl = image.originImageUrl ?: ""
|
||||||
|
LogCenter.log({ "receive image: $image" }, Level.DEBUG)
|
||||||
|
|
||||||
|
val elem = Element.newBuilder()
|
||||||
|
elem.setImage(imageElement {
|
||||||
|
this.file = md5
|
||||||
|
this.url = when (record.chatType) {
|
||||||
|
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(
|
||||||
|
originalUrl = originalUrl,
|
||||||
|
md5 = md5,
|
||||||
|
fileId = image.fileUuid,
|
||||||
|
width = image.picWidth.toUInt(),
|
||||||
|
height = image.picHeight.toUInt(),
|
||||||
|
sha = "",
|
||||||
|
fileSize = image.fileSize.toULong(),
|
||||||
|
peer = record.peerUin.toString()
|
||||||
|
)
|
||||||
|
|
||||||
|
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(
|
||||||
|
originalUrl = originalUrl,
|
||||||
|
md5 = md5,
|
||||||
|
fileId = image.fileUuid,
|
||||||
|
width = image.picWidth.toUInt(),
|
||||||
|
height = image.picHeight.toUInt(),
|
||||||
|
sha = "",
|
||||||
|
fileSize = image.fileSize.toULong(),
|
||||||
|
peer = record.senderUin.toString(),
|
||||||
|
storeId = storeId
|
||||||
|
)
|
||||||
|
|
||||||
|
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(
|
||||||
|
originalUrl = originalUrl,
|
||||||
|
md5 = md5,
|
||||||
|
fileId = image.fileUuid,
|
||||||
|
width = image.picWidth.toUInt(),
|
||||||
|
height = image.picHeight.toUInt(),
|
||||||
|
sha = "",
|
||||||
|
fileSize = image.fileSize.toULong(),
|
||||||
|
peer = record.channelId.ifNullOrEmpty { record.peerUin.toString() } ?: "0",
|
||||||
|
subPeer = record.guildId ?: "0"
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> throw UnsupportedOperationException("Not supported chat type: ${record.chatType}")
|
||||||
|
}
|
||||||
|
this.type = if (image.isFlashPic == true) ImageType.FLASH else if (image.original) ImageType.ORIGIN else ImageType.COMMON
|
||||||
|
this.subType = image.picSubType
|
||||||
|
})
|
||||||
|
|
||||||
|
return Result.success(elem.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun convertVoice(record: MsgRecord, element: MsgElement): Result<Element> {
|
||||||
|
val ptt = element.pttElement
|
||||||
|
val elem = Element.newBuilder()
|
||||||
|
|
||||||
|
val md5 = if (ptt.fileName.startsWith("silk"))
|
||||||
|
ptt.fileName.substring(5)
|
||||||
|
else ptt.md5HexStr
|
||||||
|
|
||||||
|
elem.setVoice(voiceElement {
|
||||||
|
this.url = when (record.chatType) {
|
||||||
|
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPttDownUrl("0", ptt.fileUuid)
|
||||||
|
MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupPttDownUrl("0", md5.hex2ByteArray(), ptt.fileUuid)
|
||||||
|
|
||||||
|
else -> throw UnsupportedOperationException("Not supported chat type: ${record.chatType}")
|
||||||
|
}
|
||||||
|
this.file = md5
|
||||||
|
this.magic = ptt.voiceChangeType != MsgConstant.KPTTVOICECHANGETYPENONE
|
||||||
|
})
|
||||||
|
|
||||||
|
return Result.success(elem.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun convertVideo(record: MsgRecord, element: MsgElement): Result<Element> {
|
||||||
|
val video = element.videoElement
|
||||||
|
val elem = Element.newBuilder()
|
||||||
|
val md5 = if (video.fileName.contains("/")) {
|
||||||
|
video.videoMd5.takeIf {
|
||||||
|
!it.isNullOrEmpty()
|
||||||
|
}?.hex2ByteArray() ?: video.fileName.split("/").let {
|
||||||
|
it[it.size - 2].hex2ByteArray()
|
||||||
|
}
|
||||||
|
} else video.fileName.split(".")[0].hex2ByteArray()
|
||||||
|
elem.setVideo(videoElement {
|
||||||
|
this.file = md5.toHexString()
|
||||||
|
this.url = when (record.chatType) {
|
||||||
|
MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid)
|
||||||
|
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", md5, video.fileUuid)
|
||||||
|
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid)
|
||||||
|
else -> throw UnsupportedOperationException("Not supported chat type: ${record.chatType}")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return Result.success(elem.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun convertMarketFace(record: MsgRecord, element: MsgElement): Result<Element> {
|
||||||
|
val marketFace = element.marketFaceElement
|
||||||
|
val elem = Element.newBuilder()
|
||||||
|
elem.setMarketFace(io.kritor.event.marketFaceElement {
|
||||||
|
this.id = marketFace.emojiId.lowercase()
|
||||||
|
})
|
||||||
|
return Result.success(elem.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun convertStructJson(record: MsgRecord, element: MsgElement): Result<Element> {
|
||||||
|
val data = element.arkElement.bytesData.asJsonObject
|
||||||
|
val elem = Element.newBuilder()
|
||||||
|
when (data["app"].asString) {
|
||||||
|
"com.tencent.multimsg" -> {
|
||||||
|
val info = data["meta"].asJsonObject["detail"].asJsonObject
|
||||||
|
elem.setForward(forwardElement {
|
||||||
|
this.id = info["resid"].asString
|
||||||
|
this.uniseq = info["uniseq"].asString
|
||||||
|
this.summary = info["summary"].asString
|
||||||
|
this.description = info["news"].asJsonArray.joinToString("\n") {
|
||||||
|
it.asJsonObject["text"].asString
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
"com.tencent.troopsharecard" -> {
|
||||||
|
val info = data["meta"].asJsonObject["contact"].asJsonObject
|
||||||
|
elem.setContact(contactElement {
|
||||||
|
this.scene = Scene.GROUP
|
||||||
|
this.peer = info["jumpUrl"].asString.split("group_code=")[1]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
"com.tencent.contact.lua" -> {
|
||||||
|
val info = data["meta"].asJsonObject["contact"].asJsonObject
|
||||||
|
elem.setContact(contactElement {
|
||||||
|
this.scene = Scene.FRIEND
|
||||||
|
this.peer = info["jumpUrl"].asString.split("uin=")[1]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
"com.tencent.map" -> {
|
||||||
|
val info = data["meta"].asJsonObject["Location.Search"].asJsonObject
|
||||||
|
elem.setLocation(locationElement {
|
||||||
|
this.lat = info["lat"].asString.toFloat()
|
||||||
|
this.lon = info["lng"].asString.toFloat()
|
||||||
|
this.address = info["address"].asString
|
||||||
|
this.title = info["name"].asString
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> elem.setJson(jsonElement {
|
||||||
|
this.json = data.toString()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return Result.success(elem.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun convertReply(record: MsgRecord, element: MsgElement): Result<Element> {
|
||||||
|
val reply = element.replyElement
|
||||||
|
val elem = Element.newBuilder()
|
||||||
|
elem.setReply(replyElement {
|
||||||
|
val msgSeq = reply.replayMsgSeq
|
||||||
|
val contact = MessageHelper.generateContact(record)
|
||||||
|
val sourceRecords = withTimeoutOrNull(3000) {
|
||||||
|
suspendCancellableCoroutine {
|
||||||
|
QRoute.api(IMsgService::class.java).getMsgsBySeqAndCount(contact, msgSeq, 1, true) { _, _, records ->
|
||||||
|
it.resume(records)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sourceRecords.isNullOrEmpty()) {
|
||||||
|
LogCenter.log("无法查询到回复的消息ID: seq = $msgSeq", Level.WARN)
|
||||||
|
this.messageId = reply.replayMsgId
|
||||||
|
} else {
|
||||||
|
this.messageId = sourceRecords.first().msgId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return Result.success(elem.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun convertFile(record: MsgRecord, element: MsgElement): Result<Element> {
|
||||||
|
val fileMsg = element.fileElement
|
||||||
|
val fileName = fileMsg.fileName
|
||||||
|
val fileSize = fileMsg.fileSize
|
||||||
|
val expireTime = fileMsg.expireTime ?: 0
|
||||||
|
val fileId = fileMsg.fileUuid
|
||||||
|
val bizId = fileMsg.fileBizId ?: 0
|
||||||
|
val fileSubId = fileMsg.fileSubId ?: ""
|
||||||
|
val url = when (record.chatType) {
|
||||||
|
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId)
|
||||||
|
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildFileDownUrl(record.guildId, record.channelId, fileId, bizId)
|
||||||
|
else -> RichProtoSvc.getGroupFileDownUrl(record.peerUin, fileId, bizId)
|
||||||
|
}
|
||||||
|
val elem = Element.newBuilder()
|
||||||
|
elem.setFile(io.kritor.event.fileElement {
|
||||||
|
this.name = fileName
|
||||||
|
this.size = fileSize
|
||||||
|
this.url = url
|
||||||
|
this.expireTime = expireTime
|
||||||
|
this.id = fileId
|
||||||
|
this.subId = fileSubId
|
||||||
|
this.biz = bizId
|
||||||
|
})
|
||||||
|
return Result.success(elem.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun convertMarkdown(record: MsgRecord, element: MsgElement): Result<Element> {
|
||||||
|
val markdown = element.markdownElement
|
||||||
|
val elem = Element.newBuilder()
|
||||||
|
elem.setMarkdown(io.kritor.event.markdownElement {
|
||||||
|
this.markdown = markdown.content
|
||||||
|
})
|
||||||
|
return Result.success(elem.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun convertBubbleFace(record: MsgRecord, element: MsgElement): Result<Element> {
|
||||||
|
val bubbleFace = element.faceBubbleElement
|
||||||
|
val elem = Element.newBuilder()
|
||||||
|
elem.setBubbleFace(io.kritor.event.bubbleFaceElement {
|
||||||
|
this.id = bubbleFace.yellowFaceInfo.index
|
||||||
|
this.count = bubbleFace.faceCount ?: 1
|
||||||
|
})
|
||||||
|
return Result.success(elem.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun convertInlineKeyboard(record: MsgRecord, element: MsgElement): Result<Element> {
|
||||||
|
val inlineKeyboard = element.inlineKeyboardElement
|
||||||
|
val elem = Element.newBuilder()
|
||||||
|
elem.setButton(io.kritor.event.buttonElement {
|
||||||
|
inlineKeyboard.rows.forEach { row ->
|
||||||
|
this.rows.add(io.kritor.event.row {
|
||||||
|
row.buttons.forEach buttonsLoop@ { button ->
|
||||||
|
if (button == null) return@buttonsLoop
|
||||||
|
this.buttons.add(io.kritor.event.button {
|
||||||
|
this.id = button.id
|
||||||
|
this.action = buttonAction {
|
||||||
|
this.type = button.type
|
||||||
|
this.permission = buttonActionPermission {
|
||||||
|
this.type = button.permissionType
|
||||||
|
button.specifyRoleIds?.let {
|
||||||
|
this.roleIds.addAll(it)
|
||||||
|
}
|
||||||
|
button.specifyTinyids?.let {
|
||||||
|
this.userIds.addAll(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.unsupportedTips = button.unsupportTips ?: ""
|
||||||
|
this.data = button.data ?: ""
|
||||||
|
this.reply = button.isReply
|
||||||
|
this.enter = button.enter
|
||||||
|
}
|
||||||
|
this.renderData = buttonRender {
|
||||||
|
this.label = button.label ?: ""
|
||||||
|
this.visitedLabel = button.visitedLabel ?: ""
|
||||||
|
this.style = button.style
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return Result.success(elem.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun get(case: Int): Convertor? {
|
||||||
|
return convertorMap[case]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Reference in New Issue
Block a user