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