mirror of
https://github.com/whitechi73/OpenShamrock.git
synced 2024-08-14 13:12:17 +08:00
Shamrock
: 完成消息推送
Signed-off-by: 白池 <whitechi73@outlook.com>
This commit is contained in:
parent
7bacea3288
commit
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;
|
||||||
}
|
}
|
||||||
|
@ -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,18 +1,22 @@
|
|||||||
|
@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.contact
|
|
||||||
import io.kritor.event.MessageEvent
|
import io.kritor.event.MessageEvent
|
||||||
|
import io.kritor.event.Scene
|
||||||
|
import io.kritor.event.contact
|
||||||
import io.kritor.event.messageEvent
|
import io.kritor.event.messageEvent
|
||||||
import io.kritor.sender
|
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 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 {
|
||||||
@ -51,6 +55,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 +64,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 +90,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,9 +114,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.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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>()
|
||||||
|
@ -269,7 +269,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 +291,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 +311,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 +393,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,21 @@
|
|||||||
|
@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.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)
|
||||||
@ -103,275 +75,4 @@ object AioListener: IKernelMsgListener {
|
|||||||
else -> LogCenter.log("不支持PUSH事件: ${record.chatType}")
|
else -> LogCenter.log("不支持PUSH事件: ${record.chatType}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMsgRecall(chatType: Int, peerId: String, msgId: Long) {
|
|
||||||
LogCenter.log("onMsgRecall($chatType, $peerId, $msgId)")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAddSendMsg(record: MsgRecord) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMsgInfoListUpdate(msgList: ArrayList<MsgRecord>?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTempChatInfoUpdate(tempChatInfo: TempChatInfo) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMsgAbstractUpdate(arrayList: ArrayList<MsgAbstract>?) {
|
|
||||||
//arrayList?.forEach {
|
|
||||||
// LogCenter.log("onMsgAbstractUpdate($it)", Level.WARN)
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRecvMsgSvrRspTransInfo(
|
|
||||||
j2: Long,
|
|
||||||
contact: Contact?,
|
|
||||||
i2: Int,
|
|
||||||
i3: Int,
|
|
||||||
str: String?,
|
|
||||||
bArr: ByteArray?
|
|
||||||
) {
|
|
||||||
LogCenter.log("onRecvMsgSvrRspTransInfo($j2, $contact, $i2, $i3, $str)", Level.DEBUG)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRecvS2CMsg(arrayList: ArrayList<Byte>?) {
|
|
||||||
LogCenter.log("onRecvS2CMsg(${arrayList.toString()})", Level.DEBUG)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRecvSysMsg(arrayList: ArrayList<Byte>?) {
|
|
||||||
LogCenter.log("onRecvSysMsg(${arrayList.toString()})", Level.DEBUG)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBroadcastHelperDownloadComplete(broadcastHelperTransNotifyInfo: BroadcastHelperTransNotifyInfo?) {}
|
|
||||||
|
|
||||||
override fun onBroadcastHelperProgerssUpdate(broadcastHelperTransNotifyInfo: BroadcastHelperTransNotifyInfo?) {}
|
|
||||||
|
|
||||||
override fun onChannelFreqLimitInfoUpdate(
|
|
||||||
contact: Contact?,
|
|
||||||
z: Boolean,
|
|
||||||
freqLimitInfo: FreqLimitInfo?
|
|
||||||
) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onContactUnreadCntUpdate(unreadMap: HashMap<Int, HashMap<String, UnreadCntInfo>>) {
|
|
||||||
// 推送未读消息数量
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCustomWithdrawConfigUpdate(customWithdrawConfig: CustomWithdrawConfig?) {
|
|
||||||
LogCenter.log("onCustomWithdrawConfigUpdate: " + customWithdrawConfig.toString(), Level.DEBUG)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDraftUpdate(contact: Contact?, arrayList: ArrayList<MsgElement>?, j2: Long) {
|
|
||||||
LogCenter.log("onDraftUpdate: " + contact.toString() + "|" + arrayList + "|" + j2.toString(), Level.DEBUG)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onEmojiDownloadComplete(emojiNotifyInfo: EmojiNotifyInfo?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onEmojiResourceUpdate(emojiResourceInfo: EmojiResourceInfo?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFeedEventUpdate(firstViewDirectMsgNotifyInfo: FirstViewDirectMsgNotifyInfo?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFileMsgCome(arrayList: ArrayList<MsgRecord>?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFirstViewDirectMsgUpdate(firstViewDirectMsgNotifyInfo: FirstViewDirectMsgNotifyInfo?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFirstViewGroupGuildMapping(arrayList: ArrayList<FirstViewGroupGuildInfo>?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onGrabPasswordRedBag(
|
|
||||||
i2: Int,
|
|
||||||
str: String?,
|
|
||||||
i3: Int,
|
|
||||||
recvdOrder: RecvdOrder?,
|
|
||||||
msgRecord: MsgRecord?
|
|
||||||
) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onKickedOffLine(kickedInfo: KickedInfo?) {
|
|
||||||
LogCenter.log("onKickedOffLine($kickedInfo)")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRichMediaUploadComplete(notifyInfo: FileTransNotifyInfo) {
|
|
||||||
LogCenter.log({ "[BDH] 资源上传完成(${notifyInfo.trasferStatus}, ${notifyInfo.fileId}, ${notifyInfo.msgId}, ${notifyInfo.commonFileInfo})" }, Level.DEBUG)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRecvOnlineFileMsg(arrayList: ArrayList<MsgRecord>?) {
|
|
||||||
LogCenter.log(("onRecvOnlineFileMsg" + arrayList?.joinToString { ", " }), Level.DEBUG)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRichMediaDownloadComplete(fileTransNotifyInfo: FileTransNotifyInfo) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRichMediaProgerssUpdate(fileTransNotifyInfo: FileTransNotifyInfo) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSearchGroupFileInfoUpdate(searchGroupFileResult: SearchGroupFileResult?) {
|
|
||||||
LogCenter.log("onSearchGroupFileInfoUpdate($searchGroupFileResult)", Level.DEBUG)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onGroupFileInfoAdd(groupItem: GroupItem?) {
|
|
||||||
LogCenter.log("onGroupFileInfoAdd: " + groupItem.toString(), Level.DEBUG)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onGroupFileInfoUpdate(groupFileListResult: GroupFileListResult?) {
|
|
||||||
LogCenter.log("onGroupFileInfoUpdate: " + groupFileListResult.toString(), Level.DEBUG)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onGroupGuildUpdate(groupGuildNotifyInfo: GroupGuildNotifyInfo?) {
|
|
||||||
LogCenter.log("onGroupGuildUpdate: " + groupGuildNotifyInfo.toString(), Level.DEBUG)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onGroupTransferInfoAdd(groupItem: GroupItem?) {
|
|
||||||
LogCenter.log("onGroupTransferInfoAdd: " + groupItem.toString(), Level.DEBUG)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onGroupTransferInfoUpdate(groupFileListResult: GroupFileListResult?) {
|
|
||||||
LogCenter.log("onGroupTransferInfoUpdate: " + groupFileListResult.toString(), Level.DEBUG)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onGuildInteractiveUpdate(guildInteractiveNotificationItem: GuildInteractiveNotificationItem?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onGuildMsgAbFlagChanged(guildMsgAbFlag: GuildMsgAbFlag?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onGuildNotificationAbstractUpdate(guildNotificationAbstractInfo: GuildNotificationAbstractInfo?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onHitCsRelatedEmojiResult(downloadRelateEmojiResultInfo: DownloadRelateEmojiResultInfo?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onHitEmojiKeywordResult(hitRelatedEmojiWordsResult: HitRelatedEmojiWordsResult?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onHitRelatedEmojiResult(relatedWordEmojiInfo: RelatedWordEmojiInfo?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onImportOldDbProgressUpdate(importOldDbMsgNotifyInfo: ImportOldDbMsgNotifyInfo?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onInputStatusPush(inputStatusInfo: InputStatusInfo?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLineDev(devList: ArrayList<DevInfo>?) {
|
|
||||||
//LogCenter.log("onLineDev($arrayList)")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLogLevelChanged(newLevel: Long) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMsgBoxChanged(arrayList: ArrayList<ContactMsgBoxInfo>?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMsgDelete(contact: Contact?, arrayList: ArrayList<Long>?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMsgEventListUpdate(hashMap: HashMap<String, ArrayList<Long>>?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMsgInfoListAdd(arrayList: ArrayList<MsgRecord>?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMsgQRCodeStatusChanged(i2: Int) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMsgSecurityNotify(msgRecord: MsgRecord?) {
|
|
||||||
LogCenter.log("onMsgSecurityNotify($msgRecord)")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMsgSettingUpdate(msgSetting: MsgSetting?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNtFirstViewMsgSyncEnd() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNtMsgSyncEnd() {
|
|
||||||
LogCenter.log("NTKernel同步消息完成", Level.DEBUG)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNtMsgSyncStart() {
|
|
||||||
LogCenter.log("NTKernel同步消息开始", Level.DEBUG)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onReadFeedEventUpdate(firstViewDirectMsgNotifyInfo: FirstViewDirectMsgNotifyInfo?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRecvGroupGuildFlag(i2: Int) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRecvUDCFlag(i2: Int) {
|
|
||||||
LogCenter.log("onRecvUDCFlag($i2)", Level.DEBUG)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSendMsgError(j2: Long, contact: Contact?, i2: Int, str: String?) {
|
|
||||||
LogCenter.log("onSendMsgError($j2, $contact, $j2, $str)", Level.DEBUG)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSysMsgNotification(i2: Int, j2: Long, j3: Long, arrayList: ArrayList<Byte>?) {
|
|
||||||
LogCenter.log("onSysMsgNotification($i2, $j2, $j3, $arrayList)", Level.DEBUG)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onUnreadCntAfterFirstView(hashMap: HashMap<Int, ArrayList<UnreadCntInfo>>?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onUnreadCntUpdate(hashMap: HashMap<Int, ArrayList<UnreadCntInfo>>?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onUserChannelTabStatusChanged(z: Boolean) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onUserOnlineStatusChanged(z: Boolean) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onUserTabStatusChanged(arrayList: ArrayList<TabStatusInfo>?) {
|
|
||||||
LogCenter.log("onUserTabStatusChanged($arrayList)", Level.DEBUG)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onlineStatusBigIconDownloadPush(i2: Int, j2: Long, str: String?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onlineStatusSmallIconDownloadPush(i2: Int, j2: Long, str: String?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -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(i2: Int, str: String?, j2: 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,15 @@
|
|||||||
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.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 +31,25 @@ internal object MessageHelper: QQInterfaces() {
|
|||||||
} ?: return Result.failure(Exception("获取临时会话信息失败"))
|
} ?: return Result.failure(Exception("获取临时会话信息失败"))
|
||||||
return Result.success(info)
|
return Result.success(info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun generateContact(chatType: Int, id: String, subId: String = ""): Contact {
|
||||||
|
val peerId = when (chatType) {
|
||||||
|
MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> {
|
||||||
|
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
|
||||||
}
|
}
|
405
xposed/src/main/java/qq/service/msg/MsgConvertor.kt
Normal file
405
xposed/src/main/java/qq/service/msg/MsgConvertor.kt
Normal file
@ -0,0 +1,405 @@
|
|||||||
|
package qq.service.msg
|
||||||
|
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
|
||||||
|
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.rpsElement
|
||||||
|
import io.kritor.event.textElement
|
||||||
|
import io.kritor.event.videoElement
|
||||||
|
import io.kritor.event.voiceElement
|
||||||
|
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
|
||||||
|
|
||||||
|
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(io.kritor.event.replyElement {
|
||||||
|
this.messageId = reply.replayMsgId
|
||||||
|
})
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user