diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..430402e8 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "kritor"] + path = kritor + url = https://github.com/KarinJS/kritor diff --git a/README.md b/README.md index 7d10185e..1a69a17a 100644 --- a/README.md +++ b/README.md @@ -28,24 +28,8 @@ ## 兼容|迁移|替代 说明 - 一键移植:本项目基于 go-cqhttp 的文档进行开发实现。 -- 平行部署:可多平台部署,未来将会支持 Docker 部署的教程。 - -## 相关项目 - - - - - - - - - - - - - - -
Lagrange.CoreNTQQ 的协议实现
OpenShamrock基于 Xposed 实现 OneBot 标准的机器人框架(👈你在这里
Chronocat基于 Electron 的、模块化的 Satori 框架
+- 平行部署:可多平台部署,未来将会支持 Docker 部署的教程。 +- 替代方案:[Lagrange.Core](https://github.com/LagrangeDev/Lagrange.Core) ## 权限声明 diff --git a/annotations/build.gradle.kts b/annotations/build.gradle.kts index cebd7f61..dbb3902c 100644 --- a/annotations/build.gradle.kts +++ b/annotations/build.gradle.kts @@ -9,6 +9,6 @@ java { } dependencies { - implementation(DEPENDENCY_PROTOBUF) + //implementation(DEPENDENCY_PROTOBUF) implementation(kotlinx("serialization-protobuf", "1.6.2")) } \ No newline at end of file diff --git a/annotations/src/main/java/kritor/service/Grpc.kt b/annotations/src/main/java/kritor/service/Grpc.kt new file mode 100644 index 00000000..2d6fe33d --- /dev/null +++ b/annotations/src/main/java/kritor/service/Grpc.kt @@ -0,0 +1,11 @@ +package kritor.service + +import kotlin.reflect.KClass + +@Retention(AnnotationRetention.SOURCE) +@Target(AnnotationTarget.FUNCTION) +annotation class Grpc( + val serviceName: String, + val funcName: String, + +) diff --git a/annotations/src/main/java/moe/fuqiuluo/symbols/Protobuf.kt b/annotations/src/main/java/moe/fuqiuluo/symbols/Protobuf.kt index a9e148ec..43ec4e3d 100644 --- a/annotations/src/main/java/moe/fuqiuluo/symbols/Protobuf.kt +++ b/annotations/src/main/java/moe/fuqiuluo/symbols/Protobuf.kt @@ -5,6 +5,8 @@ import kotlinx.serialization.protobuf.ProtoBuf import kotlin.reflect.KClass +val EMPTY_BYTE_ARRAY = ByteArray(0) + interface Protobuf> inline fun > ByteArray.decodeProtobuf(to: KClass? = null): T { diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 24c36669..f2031351 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -17,7 +17,7 @@ android { minSdk = 27 targetSdk = 34 versionCode = getVersionCode() - versionName = "1.0.9" + ".r${getGitCommitCount()}." + getVersionName() + versionName = "1.1.0" + ".r${getGitCommitCount()}." + getVersionName() testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { @@ -201,14 +201,8 @@ dependencies { implementation("io.coil-kt:coil-compose:2.4.0") implementation(kotlinx("io-jvm", "0.1.16")) - implementation(ktor("server", "core")) - implementation(ktor("server", "host-common")) - implementation(ktor("server", "status-pages")) - implementation(ktor("server", "netty")) - implementation(ktor("server", "content-negotiation")) implementation(ktor("client", "core")) - implementation(ktor("client", "content-negotiation")) - implementation(ktor("client", "cio")) + implementation(ktor("client", "okhttp")) implementation(ktor("serialization", "kotlinx-json")) implementation(project(":xposed")) diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index a69e8ff2..ea53a892 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -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) diff --git a/app/src/main/cpp/cqcode.cpp b/app/src/main/cpp/cqcode.cpp deleted file mode 100644 index 60a5682a..00000000 --- a/app/src/main/cpp/cqcode.cpp +++ /dev/null @@ -1,138 +0,0 @@ -#include -#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>& dest) { - std::string cache; - bool is_start = false; - std::string key_tmp; - std::unordered_map 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 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 kv; - replace_string(cache, "[", "["); - replace_string(cache, "]", "]"); - replace_string(cache, "&", "&"); - kv.emplace("_type", "text"); - kv.emplace("text", cache); - dest.push_back(kv); - } -} diff --git a/app/src/main/cpp/group_honor.cpp b/app/src/main/cpp/group_honor.cpp deleted file mode 100644 index ebae11fa..00000000 --- a/app/src/main/cpp/group_honor.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include "jni.h" -#include -#include -#include - -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_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, "", - "(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; -} diff --git a/app/src/main/cpp/interface/cqcode.h b/app/src/main/cpp/interface/cqcode.h deleted file mode 100644 index aed24f94..00000000 --- a/app/src/main/cpp/interface/cqcode.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef UNTITLED_CQCODE_H -#define UNTITLED_CQCODE_H - -#include -#include -#include -#include - -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>& dest); - -void encode_cqcode(const std::vector>& segment, std::string& dest); - -#endif //UNTITLED_CQCODE_H diff --git a/app/src/main/cpp/message.cpp b/app/src/main/cpp/message.cpp index 97fec4ae..09284d4d 100644 --- a/app/src/main/cpp/message.cpp +++ b/app/src/main/cpp/message.cpp @@ -1,5 +1,4 @@ #include "jni.h" -#include "cqcode.h" #include 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, "", "()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> dest; - try { - decode_cqcode(cppCode, dest); - } catch (illegal_code& code) { - return arrayList; - } - - jclass HashMap = env->FindClass("java/util/HashMap"); - jmethodID NewHashMap = env->GetMethodID(HashMap, "", "()V"); - jclass String = env->FindClass("java/lang/String"); - jmethodID NewString = env->GetMethodID(String, "", "([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, diff --git a/app/src/main/cpp/shamrock.cpp b/app/src/main/cpp/shamrock.cpp index 9e90b5c2..ce6a499d 100644 --- a/app/src/main/cpp/shamrock.cpp +++ b/app/src/main/cpp/shamrock.cpp @@ -12,7 +12,7 @@ extern "C" JNIEXPORT jstring JNICALL -Java_moe_fuqiuluo_shamrock_xposed_hooks_PullConfig_testNativeLibrary(JNIEnv *env, jobject thiz) { +Java_moe_fuqiuluo_shamrock_xposed_actions_interacts_Init_testNativeLibrary(JNIEnv *env, jobject thiz) { return env->NewStringUTF("加载Shamrock库成功~"); } diff --git a/app/src/main/java/moe/fuqiuluo/shamrock/MainActivity.kt b/app/src/main/java/moe/fuqiuluo/shamrock/MainActivity.kt index 5b0b1551..52f4e809 100644 --- a/app/src/main/java/moe/fuqiuluo/shamrock/MainActivity.kt +++ b/app/src/main/java/moe/fuqiuluo/shamrock/MainActivity.kt @@ -4,6 +4,7 @@ package moe.fuqiuluo.shamrock import android.annotation.SuppressLint import android.os.Bundle +import android.os.Handler import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.setContent @@ -64,7 +65,9 @@ import androidx.compose.ui.unit.sp import androidx.core.view.WindowCompat import com.google.accompanist.systemuicontroller.rememberSystemUiController import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import moe.fuqiuluo.shamrock.tools.GlobalUi import moe.fuqiuluo.shamrock.ui.app.AppRuntime import moe.fuqiuluo.shamrock.ui.app.Logger import moe.fuqiuluo.shamrock.ui.app.RuntimeState @@ -85,8 +88,16 @@ import moe.fuqiuluo.shamrock.ui.tools.getShamrockVersion class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContent { + LaunchedEffect(Unit) { + while (true) { + delay(5_000) // Delay in milliseconds + broadcastToModule { + putExtra("__cmd", "switch_status") + } + } + } + CompositionLocalProvider( LocalIndication provides NoIndication ) { @@ -96,8 +107,9 @@ class MainActivity : ComponentActivity() { isAppearanceLightStatusBars = true } WindowCompat.setDecorFitsSystemWindows(window, true) - broadcastToModule { putExtra("__cmd", "fetchPort") } } + + GlobalUi = Handler(mainLooper) } } @@ -153,7 +165,7 @@ private fun AppMainView() { } val ctx = LocalContext.current - LaunchedEffect(isFined.value) { + LaunchedEffect(isFined) { if (isFined.value) { AppRuntime.log(LocalString.logCentralLoadSuccessfully) Toast.makeText(ctx, LocalString.frameworkYes, Toast.LENGTH_SHORT).show() @@ -284,58 +296,11 @@ private fun AnimatedTab( val lastSelectedState = remember { mutableIntStateOf(0) } - val enter = remember { - scaleIn(animationSpec = TweenSpec(150, easing = FastOutLinearInEasing)) - } - val exit = remember { - scaleOut(animationSpec = TweenSpec(150, easing = FastOutSlowInEasing)) - } val defaultConst = SELECTED_TABLE[index * 2] val selectedConst = SELECTED_TABLE[(index * 2) + 1] val isFirst: Boolean = (lastSelectedState.value and defaultConst) != defaultConst - var icon: @Composable (() -> Unit)? = null - var text: @Composable (() -> Unit)? = null - - if (curSelected) { - text = { - AnimatedVisibility(visibleState = MutableTransitionState(false).also { - it.targetState = - isFirst || lastSelectedState.value and selectedConst == selectedConst - }, enter = enter, exit = exit, modifier = Modifier) { - Text( - text = titleWithIcon.first, - color = GlobalColor.TabItem, - fontSize = 15.sp, - fontWeight = FontWeight.Bold, - modifier = Modifier - .padding(bottom = 5.dp) - .indication( - remember { MutableInteractionSource() }, - rememberRipple(color = Color.Transparent) - ) - ) - } - } - } else { - icon = { - Icon( - painter = painterResource(id = titleWithIcon.second), - contentDescription = titleWithIcon.first, - tint = Color.Unspecified, - modifier = Modifier - .height(24.dp) - .width(24.dp) - .padding(bottom = 5.dp) - .indication( - remember { MutableInteractionSource() }, - rememberRipple(color = Color.Transparent) - ) - ) - } - } - ShamrockTab( selected = curSelected, onClick = { @@ -343,11 +308,13 @@ private fun AnimatedTab( state.scrollToPage(index, 0f) } }, - text = text, - icon = icon, selectedContentColor = Color.Transparent, unselectedContentColor = Color.Transparent, - indication = null + indication = null, + titleWithIcon = titleWithIcon, + visibleState = MutableTransitionState(false).also { + it.targetState = isFirst || lastSelectedState.value and selectedConst == selectedConst + } ) lastSelectedState.value.let { var tmp = it diff --git a/app/src/main/java/moe/fuqiuluo/shamrock/app/config/ConfigKey.kt b/app/src/main/java/moe/fuqiuluo/shamrock/app/config/ConfigKey.kt new file mode 100644 index 00000000..40ad8b05 --- /dev/null +++ b/app/src/main/java/moe/fuqiuluo/shamrock/app/config/ConfigKey.kt @@ -0,0 +1,2 @@ +package moe.fuqiuluo.shamrock.app.config + diff --git a/app/src/main/java/moe/fuqiuluo/shamrock/app/config/ShamrockConfig.kt b/app/src/main/java/moe/fuqiuluo/shamrock/app/config/ShamrockConfig.kt new file mode 100644 index 00000000..21c2a2dd --- /dev/null +++ b/app/src/main/java/moe/fuqiuluo/shamrock/app/config/ShamrockConfig.kt @@ -0,0 +1,33 @@ +package moe.fuqiuluo.shamrock.app.config + +import android.content.Context +import moe.fuqiuluo.shamrock.config.ConfigKey +import moe.fuqiuluo.shamrock.ui.service.internal.broadcastToModule + +object ShamrockConfig { + internal fun getConfigPref(ctx: Context) = ctx.getSharedPreferences("config", 0) + + internal inline operator fun get(ctx: Context, key: ConfigKey): Type { + val preferences = getConfigPref(ctx) + return when(Type::class) { + Int::class -> preferences.getInt(key.name(), key.default() as Int) as Type + Long::class -> preferences.getLong(key.name(), key.default() as Long) as Type + String::class -> preferences.getString(key.name(), key.default() as String) as Type + Boolean::class -> preferences.getBoolean(key.name(), key.default() as Boolean) as Type + else -> throw IllegalArgumentException("Unsupported type") + } + } + + internal inline operator fun set(ctx: Context, key: ConfigKey, value: Type) { + val preferences = getConfigPref(ctx) + val editor = preferences.edit() + when(Type::class) { + Int::class -> editor.putInt(key.name(), value as Int) + Long::class -> editor.putLong(key.name(), value as Long) + String::class -> editor.putString(key.name(), value as String) + Boolean::class -> editor.putBoolean(key.name(), value as Boolean) + else -> throw IllegalArgumentException("Unsupported type") + } + editor.apply() + } +} \ No newline at end of file diff --git a/app/src/main/java/moe/fuqiuluo/shamrock/ui/app/ShamrockConfig.kt b/app/src/main/java/moe/fuqiuluo/shamrock/ui/app/ShamrockConfig.kt deleted file mode 100644 index f12999b7..00000000 --- a/app/src/main/java/moe/fuqiuluo/shamrock/ui/app/ShamrockConfig.kt +++ /dev/null @@ -1,395 +0,0 @@ -package moe.fuqiuluo.shamrock.ui.app - -import android.content.Context -import moe.fuqiuluo.shamrock.ui.service.internal.broadcastToModule - -object ShamrockConfig { - fun getSSLKeyPath(ctx: Context): String { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getString("key_store", "")!! - } - - fun setSSLKeyPath(ctx: Context, path: String) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putString("key_store", path).apply() - pushUpdate(ctx) - } - - fun getSSLPort(ctx: Context): Int { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getInt("ssl_port", 5701) - } - - fun setSSLPort(ctx: Context, port: Int) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putInt("ssl_port", port).apply() - pushUpdate(ctx) - } - - fun getSSLAlias(ctx: Context): String { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getString("ssl_alias", "")!! - } - - fun setSSLAlias(ctx: Context, alias: String) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putString("ssl_alias", alias).apply() - pushUpdate(ctx) - } - - fun getSSLPwd(ctx: Context): String { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getString("ssl_pwd", "")!! - } - - fun setSSLPwd(ctx: Context, alias: String) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putString("ssl_pwd", alias).apply() - pushUpdate(ctx) - } - - fun getSSLPrivatePwd(ctx: Context): String { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getString("ssl_private_pwd", "")!! - } - - fun setSSLPrivatePwd(ctx: Context, alias: String) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putString("ssl_private_pwd", alias).apply() - pushUpdate(ctx) - } - - fun getHttpAddr(ctx: Context): String { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getString("http_addr", "")!! - } - - fun setHttpAddr(ctx: Context, v: String) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putString("http_addr", v).apply() - pushUpdate(ctx) - } - - fun isPro(ctx: Context): Boolean { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getBoolean("pro_api", false) - } - - fun setPro(ctx: Context, v: Boolean) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putBoolean("pro_api", v).apply() - ctx.broadcastToModule { - putExtra("type", "restart") - putExtra("__cmd", "change_port") - } - pushUpdate(ctx) - } - - fun getToken(ctx: Context): String { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getString("token", null) ?: "" - } - - fun setToken(ctx: Context, v: String?) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putString("token", v).apply() - pushUpdate(ctx) - } - - fun isWs(ctx: Context): Boolean { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getBoolean("ws", false) - } - - fun setWs(ctx: Context, v: Boolean) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putBoolean("ws", v).apply() - pushUpdate(ctx) - } - - fun isWsClient(ctx: Context): Boolean { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getBoolean("ws_client", false) - } - - fun setWsClient(ctx: Context, v: Boolean) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putBoolean("ws_client", v).apply() - pushUpdate(ctx) - } - - fun isTablet(ctx: Context): Boolean { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getBoolean("tablet", false) - } - - fun setTablet(ctx: Context, v: Boolean) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putBoolean("tablet", v).apply() - pushUpdate(ctx) - } - - fun isUseCQCode(ctx: Context): Boolean { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getBoolean("use_cqcode", false) - } - - fun setUseCQCode(ctx: Context, v: Boolean) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putBoolean("use_cqcode", v).apply() - pushUpdate(ctx) - } - - fun isWebhook(ctx: Context): Boolean { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getBoolean("webhook", false) - } - - fun setWebhook(ctx: Context, v: Boolean) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putBoolean("webhook", v).apply() - pushUpdate(ctx) - } - - fun getWsAddr(ctx: Context): String { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getString("ws_addr", "")!! - } - - fun setWsAddr(ctx: Context, v: String) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putString("ws_addr", v).apply() - pushUpdate(ctx) - } - - fun getUploadResourceGroup(ctx: Context): String { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getString("up_res_group", "100000000")!! - } - - fun setUploadResourceGroup(ctx: Context, v: String) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putString("up_res_group", v).apply() - pushUpdate(ctx) - } - - fun getHttpPort(ctx: Context): Int { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getInt("port", 5700) - } - - fun setHttpPort(ctx: Context, v: Int) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putInt("port", v).apply() - ctx.broadcastToModule { - putExtra("type", "port") - putExtra("port", v) - putExtra("__cmd", "change_port") - } - pushUpdate(ctx) - } - - fun getWsPort(ctx: Context): Int { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getInt("ws_port", 5800) - } - - fun setWsPort(ctx: Context, v: Int) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putInt("ws_port", v).apply() - ctx.broadcastToModule { - putExtra("type", "ws_port") - putExtra("port", v) - putExtra("__cmd", "change_port") - } - pushUpdate(ctx) - } - - fun is2B(ctx: Context): Boolean { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getBoolean("2B", false) - } - - fun set2B(ctx: Context, v: Boolean) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putBoolean("2B", v).apply() - } - - fun setAutoClean(ctx: Context, v: Boolean) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putBoolean("auto_clear", v).apply() - } - - fun isAutoClean(ctx: Context): Boolean { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getBoolean("auto_clear", false) - } - - fun isDebug(ctx: Context): Boolean { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getBoolean("debug", false) - } - - fun setDebug(ctx: Context, v: Boolean) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putBoolean("debug", v).apply() - } - - fun isAntiTrace(ctx: Context): Boolean { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getBoolean("anti_qq_trace", true) - } - - fun isForbidUselessProcess(ctx: Context): Boolean { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getBoolean("forbid_useless_process", false) - } - - fun setForbidUselessProcess(ctx: Context, v: Boolean) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putBoolean("forbid_useless_process", v).apply() - } - - fun setAntiTrace(ctx: Context, v: Boolean) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putBoolean("anti_qq_trace", v).apply() - } - - fun isInjectPacket(ctx: Context): Boolean { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getBoolean("inject_packet", false) - } - - fun setInjectPacket(ctx: Context, v: Boolean) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putBoolean("inject_packet", v).apply() - } - - fun enableAutoStart(ctx: Context): Boolean { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getBoolean("enable_auto_start", false) - } - - fun disableAutoSyncSetting(ctx: Context): Boolean { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getBoolean("disable_auto_sync_setting", false) - } - - fun enableAliveReply(ctx: Context): Boolean { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getBoolean("alive_reply", false) - } - - fun allowShell(ctx: Context): Boolean { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getBoolean("shell", false) - } - - fun setAutoStart(ctx: Context, v: Boolean) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putBoolean("enable_auto_start", v).apply() - } - - fun setDisableAutoSyncSetting(ctx: Context, v: Boolean) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putBoolean("disable_auto_sync_setting", v).apply() - } - - fun setAliveReply(ctx: Context, v: Boolean) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putBoolean("alive_reply", v).apply() - } - - fun setShellStatus(ctx: Context, v: Boolean) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putBoolean("shell", v).apply() - } - - fun enableSelfMsg(ctx: Context): Boolean { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getBoolean("enable_self_msg", false) - } - - fun enableOldBDH(ctx: Context): Boolean { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getBoolean("enable_old_bdh", false) - } - - fun setEnableOldBDH(ctx: Context, v: Boolean) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putBoolean("enable_old_bdh", v).apply() - } - - fun enableSyncMsgAsSentMsg(ctx: Context): Boolean { - val preferences = ctx.getSharedPreferences("config", 0) - return preferences.getBoolean("enable_sync_msg_as_sent_msg", false) - } - - fun setEnableSelfMsg(ctx: Context, v: Boolean) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putBoolean("enable_self_msg", v).apply() - } - - fun setEnableSyncMsgAsSentMsg(ctx: Context, v: Boolean) { - val preferences = ctx.getSharedPreferences("config", 0) - preferences.edit().putBoolean("enable_sync_msg_as_sent_msg", v).apply() - } - - fun getConfigMap(ctx: Context): Map { - val preferences = ctx.getSharedPreferences("config", 0) - return mapOf( - "tablet" to preferences.getBoolean("tablet", false), - "port" to preferences.getInt("port", 5700), - "ws" to preferences.getBoolean("ws", false), - "ws_port" to preferences.getInt("ws_port", 5800), - "ssl_port" to preferences.getInt("ssl_port", 5701), - "http" to preferences.getBoolean("webhook", false), - "http_addr" to preferences.getString("http_addr", ""), - "ws_client" to preferences.getBoolean("ws_client", false), - "use_cqcode" to preferences.getBoolean("use_cqcode", false), - "ws_addr" to preferences.getString("ws_addr", ""), - "ssl_alias" to preferences.getString("ssl_alias", ""), - "pro_api" to preferences.getBoolean("pro_api", false), - "token" to preferences.getString("token", null), - "ssl_pwd" to preferences.getString("ssl_pwd", ""), - "inject_packet" to preferences.getBoolean("inject_packet", false), - "debug" to preferences.getBoolean("debug", false), - "anti_qq_trace" to preferences.getBoolean("anti_qq_trace", true), - "ssl_private_pwd" to preferences.getString("ssl_private_pwd", ""), - "key_store" to preferences.getString("key_store", ""), - "enable_self_msg" to preferences.getBoolean("enable_self_msg", false), - "echo_number" to preferences.getBoolean("echo_number", false), - "shell" to preferences.getBoolean("shell", false), - "alive_reply" to preferences.getBoolean("alive_reply", false), - "enable_sync_msg_as_sent_msg" to preferences.getBoolean("enable_sync_msg_as_sent_msg", false), - "disable_auto_sync_setting" to preferences.getBoolean("disable_auto_sync_setting", false), - "forbid_useless_process" to preferences.getBoolean("forbid_useless_process", false), - "enable_old_bdh" to preferences.getBoolean("enable_old_bdh", false), - "up_res_group" to preferences.getString("up_res_group", ""), - ) - } - - fun pushUpdate(ctx: Context) { - ctx.broadcastToModule { - getConfigMap(ctx).forEach { (key, value) -> - if (value == null) { - val v: String? = null - this.putExtra(key, v) - } else { - when (value) { - is Int -> this.putExtra(key, value) - is Long -> this.putExtra(key, value) - is Short -> this.putExtra(key, value) - is Byte -> this.putExtra(key, value) - is String -> this.putExtra(key, value) - is ByteArray -> this.putExtra(key, value) - is Boolean -> this.putExtra(key, value) - is Float -> this.putExtra(key, value) - is Double -> this.putExtra(key, value) - } - } - } - putExtra("__cmd", "push_config") - } - } -} \ No newline at end of file diff --git a/app/src/main/java/moe/fuqiuluo/shamrock/ui/fragment/DashboardFragment.kt b/app/src/main/java/moe/fuqiuluo/shamrock/ui/fragment/DashboardFragment.kt index 9d195c2a..2b3bfa20 100644 --- a/app/src/main/java/moe/fuqiuluo/shamrock/ui/fragment/DashboardFragment.kt +++ b/app/src/main/java/moe/fuqiuluo/shamrock/ui/fragment/DashboardFragment.kt @@ -5,7 +5,6 @@ import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.os.Build -import android.widget.Toast import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.combinedClickable @@ -25,6 +24,7 @@ import androidx.compose.material3.Divider import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -45,10 +45,12 @@ import coil.compose.rememberAsyncImagePainter import coil.request.ImageRequest import coil.size.Size import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay import moe.fuqiuluo.shamrock.R import moe.fuqiuluo.shamrock.ui.app.AppRuntime import moe.fuqiuluo.shamrock.ui.app.Level -import moe.fuqiuluo.shamrock.ui.app.ShamrockConfig +import moe.fuqiuluo.shamrock.app.config.ShamrockConfig +import moe.fuqiuluo.shamrock.config.* import moe.fuqiuluo.shamrock.ui.theme.GlobalColor import moe.fuqiuluo.shamrock.ui.theme.LocalString import moe.fuqiuluo.shamrock.ui.theme.ThemeColor @@ -72,110 +74,6 @@ fun DashboardFragment( InformationCard(ctx) APIInfoCard(ctx) FunctionCard(scope, ctx, LocalString.functionSetting) - SSLCard(ctx) - } -} - -@Composable -private fun SSLCard(ctx: Context) { - ActionBox( - modifier = Modifier.padding(top = 12.dp), - painter = painterResource(id = R.drawable.baseline_security_24), - title = LocalString.sslSetting - ) { - Column { - Divider( - modifier = Modifier, - color = GlobalColor.Divider, - thickness = 0.2.dp - ) - - val sslPort = remember { mutableStateOf(ShamrockConfig.getSSLPort(ctx).toString()) } - TextItem( - title = "SSL端口", - desc = "端口范围在0~65565,并确保可用。", - text = sslPort, - hint = "请输入端口号", - error = "端口范围应在0~65565", - checker = { - it.isNotBlank() && kotlin.runCatching { it.toInt() in 0..65565 }.getOrDefault(false) - }, - confirm = { - val newPort = sslPort.value.toInt() - ShamrockConfig.setSSLPort(ctx, newPort) - AppRuntime.log("设置SSL(HTTP)端口为$newPort,立即生效尝试中。") - } - ) - - val keyStore = remember { mutableStateOf(ShamrockConfig.getSSLKeyPath(ctx)) } - TextItem( - title = "SSL证书", - desc = "BKS签名的证书。", - text = keyStore, - hint = "输入证书路径", - error = "证书路径不合法或不存在", - checker = { - it.isNotBlank() - }, - confirm = { - val new = keyStore.value - ShamrockConfig.setSSLKeyPath(ctx, new) - AppRuntime.log("设置SSL证书为[$new]。") - } - ) - - val alias = remember { mutableStateOf(ShamrockConfig.getSSLAlias(ctx)) } - TextItem( - title = "SSL别名", - desc = "BKS签名的别名,确保大小写区分正确。", - text = alias, - hint = "输入签名别名", - error = "别名不合法", - checker = { - it.isNotBlank() - }, - confirm = { - val new = alias.value - ShamrockConfig.setSSLAlias(ctx, new) - AppRuntime.log("设置SSL别名为[$new]。") - } - ) - - val sslPwd = remember { mutableStateOf(ShamrockConfig.getSSLPwd(ctx)) } - TextItem( - title = "SSL密码", - desc = "BKS签名的密码。", - text = sslPwd, - hint = "输入签名密码", - error = "密码不合法", - checker = { - it.isNotBlank() - }, - confirm = { - val new = sslPwd.value - ShamrockConfig.setSSLPwd(ctx, new) - AppRuntime.log("设置SSL密码为[$new]。") - } - ) - - val sslPrivatePwd = remember { mutableStateOf(ShamrockConfig.getSSLPrivatePwd(ctx)) } - TextItem( - title = "SSL Private密码", - desc = "BKS签名的Private密码。", - text = sslPrivatePwd, - hint = "输入Private密码", - error = "密码不合法", - checker = { - it.isNotBlank() - }, - confirm = { - val new = sslPrivatePwd.value - ShamrockConfig.setSSLPrivatePwd(ctx, new) - AppRuntime.log("设置SSL Private密码为[$new]。") - } - ) - - } } } @@ -195,93 +93,35 @@ private fun APIInfoCard( thickness = 0.2.dp ) - val wsPort = remember { mutableStateOf(ShamrockConfig.getWsPort(ctx).toString()) } - val port = remember { mutableStateOf(ShamrockConfig.getHttpPort(ctx).toString()) } + val rpcPort = remember { mutableStateOf(ShamrockConfig[ctx, RPCPort].toString()) } TextItem( - title = "主动HTTP端口", + title = "RPC服务端口", desc = "端口范围在0~65565,并确保可用。", - text = port, + text = rpcPort, hint = "请输入端口号", error = "端口范围应在0~65565", checker = { - it.isNotBlank() && kotlin.runCatching { it.toInt() in 0..65565 }.getOrDefault(false) && wsPort.value != it + it.isNotBlank() && kotlin.runCatching { it.toInt() in 0..65565 } + .getOrDefault(false) && rpcPort.value != it }, confirm = { - val newPort = port.value.toInt() - ShamrockConfig.setHttpPort(ctx, newPort) + val newPort = rpcPort.value.toInt() + ShamrockConfig[ctx, RPCPort] = newPort AppRuntime.log("设置主动HTTP监听端口为$newPort,立即生效尝试中。") } ) + val rpcAddress = remember { mutableStateOf(ShamrockConfig[ctx, RPCAddress]) } TextItem( - title = "主动WebSocket端口", - desc = "端口范围在0~65565,并确保可用。", - text = wsPort, - hint = "请输入端口号", - error = "端口范围应在0~65565", - checker = { - it.isNotBlank() && kotlin.runCatching { it.toInt() in 0..65565 }.getOrDefault(false) && it != port.value - }, - confirm = { - val newPort = wsPort.value.toInt() - ShamrockConfig.setWsPort(ctx, newPort) - AppRuntime.log("设置主动WebSocket监听端口为$newPort。") - } - ) - - val webHookAddress = remember { mutableStateOf(ShamrockConfig.getHttpAddr(ctx)) } - TextItem( - title = "回调HTTP地址", - desc = "例如:http://shamrock.moe:80。", - text = webHookAddress, + title = "回调RPC地址", + desc = "例如:kritor.support:8081", + text = rpcAddress, hint = "请输入回调地址", error = "输入的地址不合法", - checker = { - it.isNotBlank() - }, - confirm = { - if (it.startsWith("http://") || it.startsWith("https://")) { - ShamrockConfig.setHttpAddr(ctx, webHookAddress.value) - AppRuntime.log("设置回调HTTP地址为[${webHookAddress.value}]。") - } else { - Toast.makeText(ctx, "回调地址不合法", Toast.LENGTH_SHORT).show() - webHookAddress.value = "" - } - } - ) - - val wsAddress = remember { mutableStateOf(ShamrockConfig.getWsAddr(ctx)) } - TextItem( - title = "被动WebSocket地址", - desc = "例如:ws://shamrock.moe:81,多个使用逗号分隔。", - text = wsAddress, - hint = "请输入被动地址", - error = "输入的地址不合法", - checker = { - true - }, - confirm = { - if (it.startsWith("ws://") || it.startsWith("wss://") || it.isBlank()) { - ShamrockConfig.setWsAddr(ctx, wsAddress.value) - AppRuntime.log("设置被动WebSocket地址为[${wsAddress.value}]。") - } else { - Toast.makeText(ctx, "被动WebSocket地址不合法", Toast.LENGTH_SHORT).show() - wsAddress.value = "" - } - } - ) - - val authToken = remember { mutableStateOf(ShamrockConfig.getToken(ctx)) } - TextItem( - title = "鉴权Token", - desc = "用于鉴权的Token。", - text = authToken, - hint = "请填写鉴权token", - error = "输入的参数不合法", checker = { true }, confirm = { - ShamrockConfig.setToken(ctx, authToken.value) - AppRuntime.log("设置鉴权Token为[${authToken.value}]。") + ShamrockConfig[ctx, RPCAddress] = rpcAddress.value + AppRuntime.log("设置回调RPC地址为[${rpcAddress.value}]。") } ) @@ -314,50 +154,32 @@ private fun FunctionCard( Function( title = "强制平板模式", desc = "强制QQ使用平板模式,实现共存登录。", - isSwitch = ShamrockConfig.isTablet(ctx) + isSwitch = ShamrockConfig[ctx, ForceTablet] ) { - ShamrockConfig.setTablet(ctx, it) + ShamrockConfig[ctx, ForceTablet] = it return@Function true } Function( - title = "HTTP回调", - desc = "OneBot标准的HTTPAPI回调,Shamrock作为Client。", - isSwitch = ShamrockConfig.isWebhook(ctx) + title = "主动RPC", + desc = "Kritor协议实现RPC", + isSwitch = ShamrockConfig[ctx, ActiveRPC] ) { - ShamrockConfig.setWebhook(ctx, it) + ShamrockConfig[ctx, ActiveRPC] = it return@Function true } Function( - title = "消息格式为CQ码", - desc = "HTTPAPI回调的消息格式,关闭则为消息段。", - isSwitch = ShamrockConfig.isUseCQCode(ctx) + title = "被动RPC", + desc = "Kritor协议实现RPC", + isSwitch = ShamrockConfig[ctx, PassiveRPC] ) { - ShamrockConfig.setUseCQCode(ctx, it) - return@Function true - } - - Function( - title = "主动WebSocket", - desc = "OneBot标准WebSocket,Shamrock作为Server。", - isSwitch = ShamrockConfig.isWs(ctx) - ) { - ShamrockConfig.setWs(ctx, it) - return@Function true - } - - Function( - title = "被动WebSocket", - desc = "OneBot标准WebSocket,Shamrock作为Client。", - isSwitch = ShamrockConfig.isWsClient(ctx) - ) { - ShamrockConfig.setWsClient(ctx, it) + ShamrockConfig[ctx, PassiveRPC] = it return@Function true } run { - val uploadResourceGroup = remember { mutableStateOf(ShamrockConfig.getUploadResourceGroup(ctx)) } + val uploadResourceGroup = remember { mutableStateOf(ShamrockConfig[ctx, ResourceGroup]) } Column( modifier = Modifier .absolutePadding(left = 8.dp, right = 8.dp, top = 12.dp, bottom = 0.dp) @@ -380,23 +202,11 @@ private fun FunctionCard( }, confirm = { val groupId = uploadResourceGroup.value - ShamrockConfig.setUploadResourceGroup(ctx, groupId) + ShamrockConfig[ctx, ResourceGroup] = groupId AppRuntime.log("设置接受资源群聊为[$groupId]。") } ) } - - /* - Function( - title = "专业级接口", - desc = "如果你不知道你在做什么,请不要开启本功能。", - descColor = Color.Red, - isSwitch = ShamrockConfig.isPro(ctx) - ) { - ShamrockConfig.setPro(ctx, it) - AppRuntime.log("专业级API = $it", Level.WARN) - return@Function true - }*/ } } } diff --git a/app/src/main/java/moe/fuqiuluo/shamrock/ui/fragment/LabFragment.kt b/app/src/main/java/moe/fuqiuluo/shamrock/ui/fragment/LabFragment.kt index 3b59952f..3df5d164 100644 --- a/app/src/main/java/moe/fuqiuluo/shamrock/ui/fragment/LabFragment.kt +++ b/app/src/main/java/moe/fuqiuluo/shamrock/ui/fragment/LabFragment.kt @@ -23,7 +23,14 @@ import androidx.compose.ui.unit.sp import moe.fuqiuluo.shamrock.R import moe.fuqiuluo.shamrock.ui.app.AppRuntime import moe.fuqiuluo.shamrock.ui.app.Level -import moe.fuqiuluo.shamrock.ui.app.ShamrockConfig +import moe.fuqiuluo.shamrock.app.config.ShamrockConfig +import moe.fuqiuluo.shamrock.config.AliveReply +import moe.fuqiuluo.shamrock.config.AntiJvmTrace +import moe.fuqiuluo.shamrock.config.B2Mode +import moe.fuqiuluo.shamrock.config.DebugMode +import moe.fuqiuluo.shamrock.config.EnableOldBDH +import moe.fuqiuluo.shamrock.config.EnableSelfMessage +import moe.fuqiuluo.shamrock.ui.service.handlers.InitHandler import moe.fuqiuluo.shamrock.ui.theme.GlobalColor import moe.fuqiuluo.shamrock.ui.theme.LocalString import moe.fuqiuluo.shamrock.ui.tools.NoticeTextDialog @@ -68,9 +75,9 @@ fun LabFragment() { title = LocalString.b2Mode, desc = LocalString.b2ModeDesc, descColor = it, - isSwitch = ShamrockConfig.is2B(ctx) + isSwitch = ShamrockConfig[ctx, B2Mode] ) { - ShamrockConfig.set2B(ctx, it) + ShamrockConfig[ctx, B2Mode] = it scope.toast(ctx, LocalString.restartToast) return@Function true } @@ -79,10 +86,10 @@ fun LabFragment() { title = LocalString.showDebugLog, desc = LocalString.showDebugLogDesc, descColor = it, - isSwitch = ShamrockConfig.isDebug(ctx) + isSwitch = ShamrockConfig[ctx, DebugMode] ) { - ShamrockConfig.setDebug(ctx, it) - ShamrockConfig.pushUpdate(ctx) + ShamrockConfig[ctx, DebugMode] = it + InitHandler.update(ctx) return@Function true } } @@ -100,54 +107,13 @@ fun LabFragment() { thickness = 0.2.dp ) - Function( - title = "禁止无用进程", - desc = "禁止QQ生成无用进程浪费内存,可能造成部分功能闪退。", - descColor = color, - isSwitch = ShamrockConfig.isForbidUselessProcess(ctx) - ) { - ShamrockConfig.setForbidUselessProcess(ctx, it) - ShamrockConfig.pushUpdate(ctx) - return@Function true - } - Function( title = "自回复测试", desc = "发送[ping],机器人发送一个具有调试信息的返回。", descColor = color, - isSwitch = ShamrockConfig.enableAliveReply(ctx) + isSwitch = ShamrockConfig[ctx, AliveReply] ) { - ShamrockConfig.setAliveReply(ctx, it) - return@Function true - } - - Function( - title = "开启Shell接口", - desc = "可能导致设备被入侵,请勿随意开启。", - descColor = color, - isSwitch = ShamrockConfig.allowShell(ctx) - ) { - ShamrockConfig.setShellStatus(ctx, it) - return@Function true - } - - Function( - title = "自动唤醒QQ", - desc = "QQ进程死亡时重新打开QQ进程,前提本进程存活。", - descColor = color, - isSwitch = ShamrockConfig.enableAutoStart(ctx) - ) { - ShamrockConfig.setAutoStart(ctx, it) - return@Function true - } - - Function( - title = "禁止Shamrock同步设置", - desc = "禁止Shamrock同步设置,防止恢复手动修改后的配置文件。", - descColor = color, - isSwitch = ShamrockConfig.disableAutoSyncSetting(ctx) - ) { - ShamrockConfig.setDisableAutoSyncSetting(ctx, it) + ShamrockConfig[ctx, AliveReply] = it return@Function true } @@ -194,25 +160,14 @@ fun LabFragment() { thickness = 0.2.dp ) - Function( - title = LocalString.injectPacket, - desc = LocalString.injectPacketDesc, - descColor = color, - isSwitch = ShamrockConfig.isInjectPacket(ctx) - ) { - ShamrockConfig.setInjectPacket(ctx, it) - ShamrockConfig.pushUpdate(ctx) - return@Function true - } - Function( title = LocalString.antiTrace, desc = LocalString.antiTraceDesc, descColor = color, - isSwitch = ShamrockConfig.isAntiTrace(ctx) + isSwitch = ShamrockConfig[ctx, AntiJvmTrace] ) { - ShamrockConfig.setAntiTrace(ctx, it) - ShamrockConfig.pushUpdate(ctx) + ShamrockConfig[ctx, AntiJvmTrace] = it + scope.toast(ctx, LocalString.restartToast) return@Function true } @@ -277,21 +232,10 @@ fun LabFragment() { title = "自发消息推送", desc = "推送Bot发送的消息,未做特殊处理请勿打开。", descColor = it, - isSwitch = ShamrockConfig.enableSelfMsg(ctx) - ) { - ShamrockConfig.setEnableSelfMsg(ctx, it) - ShamrockConfig.pushUpdate(ctx) - return@Function true - } - - Function( - title = "同步消息推送类型异换", - desc = "推送来自同号异设备消息,将同步消息作为自发消息推送。", - descColor = it, - isSwitch = ShamrockConfig.enableSyncMsgAsSentMsg(ctx) + isSwitch = ShamrockConfig[ctx, EnableSelfMessage] ) { - ShamrockConfig.setEnableSyncMsgAsSentMsg(ctx, it) - ShamrockConfig.pushUpdate(ctx) + ShamrockConfig[ctx, EnableSelfMessage] = it + InitHandler.update(ctx) return@Function true } @@ -299,10 +243,10 @@ fun LabFragment() { title = "启用旧版资源上传系统", desc = "如果NT内核无法上传资源,请打开本开关。", descColor = it, - isSwitch = ShamrockConfig.enableOldBDH(ctx) + isSwitch = ShamrockConfig[ctx, EnableOldBDH] ) { - ShamrockConfig.setEnableOldBDH(ctx, it) - ShamrockConfig.pushUpdate(ctx) + ShamrockConfig[ctx, EnableOldBDH] = it + InitHandler.update(ctx) return@Function true } } diff --git a/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/DashboardInitializer.kt b/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/DashboardInitializer.kt deleted file mode 100644 index ef906d65..00000000 --- a/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/DashboardInitializer.kt +++ /dev/null @@ -1,108 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) -package moe.fuqiuluo.shamrock.ui.service - -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.os.Bundle -import androidx.core.content.ContextCompat.startActivity -import io.ktor.client.request.get -import io.ktor.client.request.parameter -import io.ktor.client.request.url -import io.ktor.client.statement.bodyAsText -import io.ktor.http.HttpStatusCode -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import kotlinx.serialization.json.Json -import moe.fuqiuluo.shamrock.remote.structures.CommonResult -import moe.fuqiuluo.shamrock.remote.structures.CurrentAccount -import moe.fuqiuluo.shamrock.remote.structures.Status -import moe.fuqiuluo.shamrock.tools.GlobalClient -import moe.fuqiuluo.shamrock.ui.app.AppRuntime.AccountInfo -import moe.fuqiuluo.shamrock.ui.app.AppRuntime.log -import moe.fuqiuluo.shamrock.ui.app.AppRuntime.state -import moe.fuqiuluo.shamrock.ui.app.Level -import moe.fuqiuluo.shamrock.ui.app.ShamrockConfig -import moe.fuqiuluo.shamrock.ui.service.internal.broadcastToModule -import java.net.ConnectException -import java.util.Timer -import kotlin.concurrent.timer - -object DashboardInitializer { - private var servicePort: Int = 0 - private lateinit var heartbeatTimer: Timer - - operator fun invoke(context: Context, port: Int) { - servicePort = port - initHeartbeat(true, context) - } - - private fun initHeartbeat(reload: Boolean, context: Context) { - if (::heartbeatTimer.isInitialized && !reload) { - return - } - if (::heartbeatTimer.isInitialized) { - heartbeatTimer.cancel() - } - heartbeatTimer = timer("heartbeat", false, 0, 1000L * 30) { - checkService(context) - } - } - - private fun checkService(context: Context) { - GlobalScope.launch { - try { - GlobalClient.get { - url("http://127.0.0.1:$servicePort/get_account_info") - val token = ShamrockConfig.getToken(context) - if (token.isNotBlank()) { - //header("Authorization", "Bearer $token") - parameter("access_token", token) - } - }.let { - if (it.status == HttpStatusCode.OK) { - val result: CommonResult = Json.decodeFromString(it.bodyAsText()) - state.isFined.value = result.retcode == 0 - if (result.retcode == Status.InternalHandlerError.code) { - log("账号未登录。", Level.WARN) - } else if (result.retcode != 0) { - log("尝试从接口获取账号信息失败,未知错误。", Level.ERROR) - } else { - AccountInfo.let { account -> - account.uin.value = result.data.uin.toString() - account.nick.value = result.data.nick - } - } - } else { - state.isFined.value = false - log("尝试从接口获取账号信息失败,服务运行异常。", Level.ERROR) - } - } - } catch (e: ConnectException) { - state.isFined.value = false - context.broadcastToModule { - putExtra("__cmd", "checkAndStartService") - } - - if (ShamrockConfig.enableAutoStart(context)) { - log("检测到Service死亡,正在尝试重新启动!") - GlobalScope.launch(Dispatchers.Main) { - val packageName = "com.tencent.mobileqq" - val className = "com.tencent.mobileqq.activity.SplashActivity" - - val intent = Intent() - intent.component = ComponentName(packageName, className) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK - intent.putExtra("from", "shamrock") - startActivity(context, intent, Bundle.EMPTY) - } - } - } catch (e: Throwable) { - state.isFined.value = false - log(e.stackTraceToString(), Level.ERROR) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/handlers/FetchPortHandler.kt b/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/handlers/FetchPortHandler.kt deleted file mode 100644 index 5b8f9a1d..00000000 --- a/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/handlers/FetchPortHandler.kt +++ /dev/null @@ -1,15 +0,0 @@ -package moe.fuqiuluo.shamrock.ui.service.handlers - -import android.content.ContentValues -import android.content.Context -import moe.fuqiuluo.shamrock.ui.app.AppRuntime -import moe.fuqiuluo.shamrock.ui.service.DashboardInitializer - -object FetchPortHandler: ModuleHandler() { - override val cmd: String = "success" - - override fun onReceive(callbackId: Int, values: ContentValues, context: Context) { - AppRuntime.state.supportVoice.value = values.getAsBoolean("voice") - DashboardInitializer(context, values.getAsInteger("port")) - } -} \ No newline at end of file diff --git a/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/handlers/InitHandler.kt b/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/handlers/InitHandler.kt index 1147cb2c..349d5552 100644 --- a/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/handlers/InitHandler.kt +++ b/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/handlers/InitHandler.kt @@ -3,13 +3,37 @@ package moe.fuqiuluo.shamrock.ui.service.handlers import android.content.ContentValues import android.content.Context import moe.fuqiuluo.shamrock.ui.app.AppRuntime -import moe.fuqiuluo.shamrock.ui.app.ShamrockConfig +import moe.fuqiuluo.shamrock.app.config.ShamrockConfig +import moe.fuqiuluo.shamrock.config.* internal object InitHandler: ModuleHandler() { override val cmd: String = "init" override fun onReceive(callbackId: Int, values: ContentValues, context: Context) { + update(context) + } + + fun update(context: Context) { AppRuntime.log("推送QQ进程初始化设置数据包成功...") - callback(context, callbackId, ShamrockConfig.getConfigMap(context)) + + val maps = hashMapOf() + + ActiveRPC.update(context, maps) + AliveReply.update(context, maps) + AntiJvmTrace.update(context, maps) + DebugMode.update(context, maps) + EnableOldBDH.update(context, maps) + EnableSelfMessage.update(context, maps) + ForceTablet.update(context, maps) + PassiveRPC.update(context, maps) + ResourceGroup.update(context, maps) + RPCAddress.update(context, maps) + RPCPort.update(context, maps) + + callback(context, 1, maps) + } + + private inline fun ConfigKey.update(context: Context, map: HashMap) { + map[name()] = ShamrockConfig[context, this] } } \ No newline at end of file diff --git a/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/handlers/ModuleHandler.kt b/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/handlers/ModuleHandler.kt index 547b0741..c13b6552 100644 --- a/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/handlers/ModuleHandler.kt +++ b/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/handlers/ModuleHandler.kt @@ -29,6 +29,7 @@ abstract class ModuleHandler { } } } + putExtra("__cmd", cmd) putExtra("__hash", callbackId) } } diff --git a/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/handlers/SwitchStatus.kt b/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/handlers/SwitchStatus.kt new file mode 100644 index 00000000..52b24f88 --- /dev/null +++ b/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/handlers/SwitchStatus.kt @@ -0,0 +1,49 @@ +package moe.fuqiuluo.shamrock.ui.service.handlers + +import android.content.ContentValues +import android.content.Context +import android.widget.Toast +import moe.fuqiuluo.shamrock.tools.GlobalUi +import moe.fuqiuluo.shamrock.ui.app.AppRuntime +import java.util.Timer +import kotlin.concurrent.timer +import kotlin.concurrent.timerTask + +object SwitchStatus: ModuleHandler() { + override val cmd: String + get() = "switch_status" + + private var lastActiveTime = 0L + private var timer: Timer? = null + + override fun onReceive(callbackId: Int, values: ContentValues, context: Context) { + val voiceSwitch = values.getAsBoolean("voice") + val nickname = values.getAsString("nickname") + val account = values.getAsString("account") + if (lastActiveTime == 0L) GlobalUi.post { + Toast.makeText(context, "激活成功", Toast.LENGTH_SHORT).show() + } + AppRuntime.state.apply { + isFined.value = true + coreVersion.value = values.getAsString("core_version") + coreName.value = "LSPosed" + supportVoice.value = voiceSwitch + } + AppRuntime.AccountInfo.apply { + uin.value = account + nick.value = nickname + } + lastActiveTime = System.currentTimeMillis() + startTimer() + } + + private fun startTimer() { + timer?.cancel() + timer = timer("SwitchStatus", true, 0, 5_000) { + if (lastActiveTime != 0L && System.currentTimeMillis() - lastActiveTime > 30 * 1000) { + AppRuntime.state.isFined.value = false + lastActiveTime = 0 + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/internal/MultifunctionalProvider.kt b/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/internal/MultifunctionalProvider.kt index bb1aba19..4ccc1e82 100644 --- a/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/internal/MultifunctionalProvider.kt +++ b/app/src/main/java/moe/fuqiuluo/shamrock/ui/service/internal/MultifunctionalProvider.kt @@ -5,9 +5,9 @@ import android.content.ContentValues import android.content.Context import android.content.Intent import android.database.Cursor -import android.net.Uri import moe.fuqiuluo.shamrock.ui.service.ModuleTalker import moe.fuqiuluo.shamrock.ui.service.handlers.* +import android.net.Uri class MultifunctionalProvider: ContentProvider() { override fun insert(uri: Uri, content: ContentValues?): Uri { @@ -28,8 +28,8 @@ class MultifunctionalProvider: ContentProvider() { override fun onCreate(): Boolean { ModuleTalker.register(InitHandler) - ModuleTalker.register(FetchPortHandler) ModuleTalker.register(LogHandler) + ModuleTalker.register(SwitchStatus) return true } @@ -58,7 +58,7 @@ class MultifunctionalProvider: ContentProvider() { inline fun Context.broadcastToModule(intentBuilder: Intent.() -> Unit) { val intent = Intent() - intent.action = "moe.fuqiuluo.xqbot.dynamic" + intent.action = "moe.fuqiuluo.kritor.dynamic" intent.intentBuilder() sendBroadcast(intent) } \ No newline at end of file diff --git a/app/src/main/java/moe/fuqiuluo/shamrock/ui/tools/tabs.kt b/app/src/main/java/moe/fuqiuluo/shamrock/ui/tools/tabs.kt index ec54545a..effdf1d0 100644 --- a/app/src/main/java/moe/fuqiuluo/shamrock/ui/tools/tabs.kt +++ b/app/src/main/java/moe/fuqiuluo/shamrock/ui/tools/tabs.kt @@ -1,23 +1,35 @@ package moe.fuqiuluo.shamrock.ui.tools +import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateColor +import androidx.compose.animation.core.FastOutLinearInEasing +import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.MutableTransitionState +import androidx.compose.animation.core.TweenSpec import androidx.compose.animation.core.tween import androidx.compose.animation.core.updateTransition +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut import androidx.compose.foundation.Indication import androidx.compose.foundation.background +import androidx.compose.foundation.indication import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.selection.selectable import androidx.compose.material.ripple.rememberRipple +import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProvideTextStyle +import androidx.compose.material3.Text import androidx.compose.material3.Typography import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -31,8 +43,10 @@ import androidx.compose.ui.layout.LastBaseline import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.Placeable import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.dp @@ -135,20 +149,18 @@ private fun TabBaselineLayout( text: @Composable (() -> Unit)?, icon: @Composable (() -> Unit)? ) { - Layout( - { - if (text != null) { - Box( - Modifier - .layoutId("text") - .padding(horizontal = HorizontalTextPadding) - ) { text() } - } - if (icon != null) { - Box(Modifier.layoutId("icon")) { icon() } - } + Layout({ + if (text != null) { + Box( + Modifier + .layoutId("text") + .padding(horizontal = HorizontalTextPadding) + ) { text() } } - ) { measurables, constraints -> + if (icon != null) { + Box(Modifier.layoutId("icon")) { icon() } + } + }) { measurables, constraints -> val textPlaceable = text?.let { measurables.first { it.layoutId == "text" }.measure( // Measure with loose constraints for height as we don't want the text to take up more @@ -247,21 +259,66 @@ fun ShamrockTab( onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, - text: @Composable (() -> Unit)? = null, - icon: @Composable (() -> Unit)? = null, selectedContentColor: Color = GlobalColor.TabSelected, unselectedContentColor: Color = selectedContentColor, indication: Indication? = rememberRipple(bounded = true, color = selectedContentColor), - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + titleWithIcon: Pair, + visibleState: MutableTransitionState ) { - val styledText: @Composable (() -> Unit)? = text?.let { - @Composable { - val style = - MaterialTheme.typography.fromToken(PrimaryNavigationTabTokens.LabelTextFont) - .copy(textAlign = TextAlign.Center) - ProvideTextStyle(style, content = text) + var text: @Composable (() -> Unit)? = null + var icon: @Composable (() -> Unit)? = null + + if (!selected) { + icon = { + Icon( + painter = painterResource(id = titleWithIcon.second), + contentDescription = titleWithIcon.first, + tint = Color.Unspecified, + modifier = Modifier + .height(24.dp) + .width(24.dp) + .padding(bottom = 5.dp) + .indication( + remember { MutableInteractionSource() }, + rememberRipple(color = Color.Transparent) + ) + ) + } + } else { + text = { + val style = MaterialTheme.typography + .fromToken(PrimaryNavigationTabTokens.LabelTextFont) + .copy(textAlign = TextAlign.Center) + + ProvideTextStyle(style) { + AnimatedVisibility( + visibleState = visibleState, + enter = remember { + scaleIn(animationSpec = TweenSpec(150, easing = FastOutLinearInEasing)) + }, + exit = remember { + scaleOut(animationSpec = TweenSpec(150, easing = FastOutSlowInEasing)) + }, + modifier = Modifier + ) { + Text( + text = titleWithIcon.first, + color = GlobalColor.TabItem, + fontSize = 15.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier + .padding(bottom = 5.dp) + .indication( + remember { MutableInteractionSource() }, + rememberRipple(color = Color.Transparent) + ) + ) + } + } } } + ShamrockTab( selected, onClick, @@ -272,7 +329,10 @@ fun ShamrockTab( interactionSource, indication ) { - TabBaselineLayout(icon = icon, text = styledText) + TabBaselineLayout( + icon = icon, + text = text + ) } } diff --git a/build.gradle.kts b/build.gradle.kts index bd278cb6..6561f5bc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.2.1" apply false + id("com.android.application") version "8.2.0" apply false id("org.jetbrains.kotlin.android") version "1.9.22" apply false - id("com.android.library") version "8.2.1" apply false + id("com.android.library") version "8.2.0" apply false } diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 13e0d4cc..b2897283 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -7,10 +7,6 @@ val DEPENDENCY_ANDROIDX = arrayOf( "androidx.activity:activity-compose:1.7.2", ) -const val DEPENDENCY_JSON5K = "io.github.xn32:json5k:0.3.0" -const val DEPENDENCY_PROTOBUF = "com.google.protobuf:protobuf-java:3.24.0" -const val DEPENDENCY_JAVA_WEBSOCKET = "org.java-websocket:Java-WebSocket:1.5.4" - fun room(name: String) = "androidx.room:room-$name:${Versions.roomVersion}" fun kotlinx(name: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$name:$version" diff --git a/processor/src/main/java/moe/fuqiuluo/ksp/impl/OneBotHandlerProcessor.kt b/processor/src/main/java/moe/fuqiuluo/ksp/impl/OneBotHandlerProcessor.kt deleted file mode 100644 index ee95e5f4..00000000 --- a/processor/src/main/java/moe/fuqiuluo/ksp/impl/OneBotHandlerProcessor.kt +++ /dev/null @@ -1,85 +0,0 @@ -@file:OptIn(KspExperimental::class) -@file:Suppress("LocalVariableName", "UNCHECKED_CAST") - -package moe.fuqiuluo.ksp.impl - -import com.google.devtools.ksp.KspExperimental -import com.google.devtools.ksp.getAnnotationsByType -import com.google.devtools.ksp.getClassDeclarationByName -import com.google.devtools.ksp.getKotlinClassByName -import com.google.devtools.ksp.processing.CodeGenerator -import com.google.devtools.ksp.processing.Dependencies -import com.google.devtools.ksp.processing.KSPLogger -import com.google.devtools.ksp.processing.Resolver -import com.google.devtools.ksp.processing.SymbolProcessor -import com.google.devtools.ksp.symbol.ClassKind -import com.google.devtools.ksp.symbol.KSAnnotated -import com.google.devtools.ksp.symbol.KSClassDeclaration -import com.google.devtools.ksp.symbol.KSVisitorVoid -import com.google.devtools.ksp.validate -import com.squareup.kotlinpoet.FileSpec -import com.squareup.kotlinpoet.FunSpec -import moe.fuqiuluo.symbols.OneBotHandler - -class OneBotHandlerProcessor( - private val codeGenerator: CodeGenerator, - private val logger: KSPLogger -): SymbolProcessor { - override fun process(resolver: Resolver): List { - val ActionManagerNode = resolver.getClassDeclarationByName("moe.fuqiuluo.shamrock.remote.action.ActionManager") - ?: resolver.getKotlinClassByName("moe.fuqiuluo.shamrock.remote.action.ActionManager") - ?: resolver.getClassDeclarationByName("ActionManager") - val symbols = resolver.getSymbolsWithAnnotation(OneBotHandler::class.qualifiedName!!) - val unableToProcess = symbols.filterNot { it.validate() } - if (ActionManagerNode != null) { - val oneBotHandlers = (symbols.filter { - it is KSClassDeclaration && it.validate() && it.classKind == ClassKind.OBJECT - } as Sequence).toList() - - if (oneBotHandlers.isNotEmpty()) { - ActionManagerNode.accept(ActionManagerVisitor(oneBotHandlers), Unit) - } - } - - return unableToProcess.toList() - } - - inner class ActionManagerVisitor( - private val actionHandlers: List - ): KSVisitorVoid() { - override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) { - val packageName = classDeclaration.packageName.asString() - - // generate kotlin `init { }` - val fileSpec = FileSpec.builder(packageName, classDeclaration.qualifiedName?.asString() ?: run { - throw IllegalStateException("ActionManagerVisitor: classDeclaration.qualifiedName is null") - }).addFunction(FunSpec.builder("initManager").apply { - actionHandlers.forEach { handler -> - // fetch the params of the annotation - val annotation = handler.getAnnotationsByType(OneBotHandler::class).first() - val actionName = annotation.actionName - val alias = annotation.alias - alias.forEach { name -> - addStatement("actionMap[\"$name\"] = ${handler.simpleName.asString()}") - } - addStatement("actionMap[\"$actionName\"] = ${handler.simpleName.asString()}") - } - }.build()).apply { - addImport("moe.fuqiuluo.shamrock.remote.action.ActionManager", "actionMap") - actionHandlers.forEach { - addImport(it.packageName.asString(), it.simpleName.asString()) - } - }.build() - - codeGenerator.createNewFile( - dependencies = Dependencies(aggregating = false), - packageName = packageName, - fileName = "Auto" + classDeclaration.simpleName.asString() - ).use { outputStream -> - outputStream.writer().use { - fileSpec.writeTo(it) - } - } - } - } -} \ No newline at end of file diff --git a/processor/src/main/java/moe/fuqiuluo/ksp/providers/OneBotHandlerProcessorProvider.kt b/processor/src/main/java/moe/fuqiuluo/ksp/providers/OneBotHandlerProcessorProvider.kt deleted file mode 100644 index 102d5466..00000000 --- a/processor/src/main/java/moe/fuqiuluo/ksp/providers/OneBotHandlerProcessorProvider.kt +++ /dev/null @@ -1,17 +0,0 @@ -package moe.fuqiuluo.ksp.providers - -import com.google.auto.service.AutoService -import com.google.devtools.ksp.processing.SymbolProcessor -import com.google.devtools.ksp.processing.SymbolProcessorEnvironment -import com.google.devtools.ksp.processing.SymbolProcessorProvider -import moe.fuqiuluo.ksp.impl.OneBotHandlerProcessor - -@AutoService(SymbolProcessorProvider::class) -class OneBotHandlerProcessorProvider: SymbolProcessorProvider { - override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { - return OneBotHandlerProcessor( - environment.codeGenerator, - environment.logger - ) - } -} \ No newline at end of file diff --git a/xposed/src/main/aidl/moe/fuqiuluo/shamrock/xposed/ipc/bytedata/IByteData.aidl b/xposed/src/main/aidl/moe/fuqiuluo/shamrock/xposed/ipc/bytedata/IByteData.aidl deleted file mode 100644 index 7f1d4c26..00000000 --- a/xposed/src/main/aidl/moe/fuqiuluo/shamrock/xposed/ipc/bytedata/IByteData.aidl +++ /dev/null @@ -1,8 +0,0 @@ -// IByteData.aidl -package moe.fuqiuluo.shamrock.xposed.ipc.bytedata; - -import moe.fuqiuluo.shamrock.xposed.ipc.bytedata.IByteDataSign; - -interface IByteData { - IByteDataSign sign(String uin, String data, in byte[] salt); -} \ No newline at end of file diff --git a/xposed/src/main/aidl/moe/fuqiuluo/shamrock/xposed/ipc/bytedata/IByteDataSign.aidl b/xposed/src/main/aidl/moe/fuqiuluo/shamrock/xposed/ipc/bytedata/IByteDataSign.aidl deleted file mode 100644 index b31eec06..00000000 --- a/xposed/src/main/aidl/moe/fuqiuluo/shamrock/xposed/ipc/bytedata/IByteDataSign.aidl +++ /dev/null @@ -1,4 +0,0 @@ -// IByteDataSign.aidl -package moe.fuqiuluo.shamrock.xposed.ipc.bytedata; - -parcelable IByteDataSign; \ No newline at end of file diff --git a/xposed/src/main/aidl/moe/fuqiuluo/shamrock/xposed/ipc/qsign/IQSign.aidl b/xposed/src/main/aidl/moe/fuqiuluo/shamrock/xposed/ipc/qsign/IQSign.aidl deleted file mode 100644 index d4083741..00000000 --- a/xposed/src/main/aidl/moe/fuqiuluo/shamrock/xposed/ipc/qsign/IQSign.aidl +++ /dev/null @@ -1,4 +0,0 @@ -// IQSign.aidl -package moe.fuqiuluo.shamrock.xposed.ipc.qsign; - -parcelable IQSign; \ No newline at end of file diff --git a/xposed/src/main/aidl/moe/fuqiuluo/shamrock/xposed/ipc/qsign/IQSigner.aidl b/xposed/src/main/aidl/moe/fuqiuluo/shamrock/xposed/ipc/qsign/IQSigner.aidl deleted file mode 100644 index 6007e73c..00000000 --- a/xposed/src/main/aidl/moe/fuqiuluo/shamrock/xposed/ipc/qsign/IQSigner.aidl +++ /dev/null @@ -1,14 +0,0 @@ -// IQSigner.aidl -package moe.fuqiuluo.shamrock.xposed.ipc.qsign; - -import moe.fuqiuluo.shamrock.xposed.ipc.qsign.IQSign; - -interface IQSigner { - IQSign sign(String cmd, int seq, String uin, in byte[] buffer); - - byte[] energy(String module, in byte[] salt); - - byte[] xwDebugId(String uin, String start, String end); - - List getCmdWhiteList(); -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/BaseSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/BaseSvc.kt deleted file mode 100644 index d66c70d1..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/BaseSvc.kt +++ /dev/null @@ -1,201 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) - -package moe.fuqiuluo.qqinterface.servlet - -import android.os.Bundle -import com.tencent.mobileqq.app.QQAppInterface -import com.tencent.mobileqq.msf.core.MsfCore -import com.tencent.mobileqq.pb.ByteStringMicro -import com.tencent.qphone.base.remote.ToServiceMsg -import io.ktor.utils.io.core.BytePacketBuilder -import io.ktor.utils.io.core.readBytes -import io.ktor.utils.io.core.writeFully -import io.ktor.utils.io.core.writeInt -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.launch -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.withTimeoutOrNull -import kotlinx.serialization.encodeToByteArray - -import moe.fuqiuluo.shamrock.tools.slice -import moe.fuqiuluo.shamrock.utils.DeflateTools -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher -import moe.fuqiuluo.shamrock.xposed.helper.internal.DynamicReceiver -import moe.fuqiuluo.shamrock.xposed.helper.internal.IPCRequest -import protobuf.oidb.TrpcOidb -import mqq.app.MobileQQ -import protobuf.auto.toByteArray -import tencent.im.oidb.oidb_sso -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.resume - -internal abstract class BaseSvc { - companion object Default: CoroutineScope { - val currentUin: String - get() = app.currentAccountUin - - val app: QQAppInterface - get() = AppRuntimeFetcher.appRuntime as QQAppInterface - - fun createToServiceMsg(cmd: String): ToServiceMsg { - return ToServiceMsg("mobileqq.service", app.currentAccountUin, cmd) - } - - suspend fun sendOidbAW(cmd: String, cmdId: Int, serviceId: Int, data: ByteArray, trpc: Boolean = false, timeout: Long = 5000L): ByteArray? { - val seq = MsfCore.getNextSeq() - val buffer = withTimeoutOrNull(timeout) { - suspendCancellableCoroutine { continuation -> - launch(Dispatchers.Default) { - DynamicReceiver.register(IPCRequest(cmd, seq) { - val buffer = it.getByteArrayExtra("buffer")!! - continuation.resume(buffer) - }) - } - if (trpc) sendTrpcOidb(cmd, cmdId, serviceId, data, seq) - else sendOidb(cmd, cmdId, serviceId, data, seq) - } - }.also { - if (it == null) - DynamicReceiver.unregister(seq) - }?.copyOf() - try { - if (buffer != null && buffer.size >= 5 && buffer[4] == 120.toByte()) { - val builder = BytePacketBuilder() - val deBuffer = DeflateTools.uncompress(buffer.slice(4)) - builder.writeInt(deBuffer.size) - builder.writeFully(deBuffer) - return builder.build().readBytes() - } - } catch (_: Exception) { } - return buffer - } - - suspend fun sendBufferAW(cmd: String, isPb: Boolean, data: ByteArray, timeout: Long = 5000L): ByteArray? { - val seq = MsfCore.getNextSeq() - val buffer = withTimeoutOrNull(timeout) { - suspendCancellableCoroutine { continuation -> - launch(Dispatchers.Default) { - DynamicReceiver.register(IPCRequest(cmd, seq) { - val buffer = it.getByteArrayExtra("buffer")!! - continuation.resume(buffer) - }) - sendBuffer(cmd, isPb, data, seq) - } - } - }.also { - if (it == null) - DynamicReceiver.unregister(seq) - }?.copyOf() - try { - if (buffer != null && buffer.size >= 5 && buffer[4] == 120.toByte()) { - val builder = BytePacketBuilder() - val deBuffer = DeflateTools.uncompress(buffer.slice(4)) - builder.writeInt(deBuffer.size) - builder.writeFully(deBuffer) - return builder.build().readBytes() - } - } catch (_: Exception) { } - return buffer - } - - fun sendOidb(cmd: String, cmdId: Int, serviceId: Int, buffer: ByteArray, seq: Int = -1, trpc: Boolean = false) { - if (trpc) { - sendTrpcOidb(cmd, cmdId, serviceId, buffer, seq) - return - } - val to = createToServiceMsg(cmd) - val oidb = oidb_sso.OIDBSSOPkg() - oidb.uint32_command.set(cmdId) - oidb.uint32_service_type.set(serviceId) - oidb.bytes_bodybuffer.set(ByteStringMicro.copyFrom(buffer)) - oidb.str_client_version.set(PlatformUtils.getClientVersion(MobileQQ.getContext())) - to.putWupBuffer(oidb.toByteArray()) - to.addAttribute("req_pb_protocol_flag", true) - if (seq != -1) { - to.addAttribute("shamrock_seq", seq) - } - app.sendToService(to) - } - - fun sendTrpcOidb(cmd: String, cmdId: Int, serviceId: Int, buffer: ByteArray, seq: Int = -1) { - val to = createToServiceMsg(cmd) - - val oidb = TrpcOidb( - cmd = cmdId, - service = serviceId, - buffer = buffer, - flag = 1 - ) - to.putWupBuffer(oidb.toByteArray()) - - to.addAttribute("req_pb_protocol_flag", true) - if (seq != -1) { - to.addAttribute("shamrock_seq", seq) - } - app.sendToService(to) - } - - fun sendBuffer(cmd: String, isPb: Boolean, buffer: ByteArray, seq: Int = MsfCore.getNextSeq()) { - val toServiceMsg = ToServiceMsg("mobileqq.service", app.currentUin, cmd) - toServiceMsg.putWupBuffer(buffer) - toServiceMsg.addAttribute("req_pb_protocol_flag", isPb) - toServiceMsg.addAttribute("shamrock_seq", seq) - app.sendToService(toServiceMsg) - } - - @OptIn(ExperimentalCoroutinesApi::class) - override val coroutineContext: CoroutineContext by lazy { - Dispatchers.IO.limitedParallelism(12) - } - } - - protected fun send(toServiceMsg: ToServiceMsg) { - app.sendToService(toServiceMsg) - } - - protected suspend fun sendAW(toServiceMsg: ToServiceMsg, timeout: Long = 5000L): ByteArray? { - val seq = MsfCore.getNextSeq() - val buffer = withTimeoutOrNull(timeout) { - suspendCancellableCoroutine { continuation -> - launch(Dispatchers.Default) { - DynamicReceiver.register(IPCRequest(toServiceMsg.serviceCmd, seq) { - val buffer = it.getByteArrayExtra("buffer")!! - continuation.resume(buffer) - }) - toServiceMsg.addAttribute("shamrock_seq", seq) - send(toServiceMsg) - } - } - }.also { - if (it == null) DynamicReceiver.unregister(seq) - }?.copyOf() - try { - if (buffer != null && buffer.size >= 5 && buffer[4] == 120.toByte()) { - val builder = BytePacketBuilder() - val deBuffer = DeflateTools.uncompress(buffer.slice(4)) - builder.writeInt(deBuffer.size) - builder.writeFully(deBuffer) - return builder.build().readBytes() - } - } catch (_: Exception) { } - return buffer - } - - protected fun sendExtra(cmd: String, builder: (Bundle) -> Unit) { - val toServiceMsg = createToServiceMsg(cmd) - builder(toServiceMsg.extraData) - app.sendToService(toServiceMsg) - } - - protected fun sendPb(cmd: String, buffer: ByteArray, seq: Int) { - val toServiceMsg = createToServiceMsg(cmd) - toServiceMsg.putWupBuffer(buffer) - toServiceMsg.addAttribute("req_pb_protocol_flag", true) - toServiceMsg.addAttribute("shamrock_seq", seq) - app.sendToService(toServiceMsg) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/CardSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/CardSvc.kt deleted file mode 100644 index a113b746..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/CardSvc.kt +++ /dev/null @@ -1,135 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet - -import VIP.GetCustomOnlineStatusReq -import VIP.GetCustomOnlineStatusRsp -import com.qq.jce.wup.UniPacket -import com.tencent.mobileqq.data.Card -import com.tencent.mobileqq.profilecard.api.IProfileDataService -import com.tencent.mobileqq.profilecard.api.IProfileProtocolService -import com.tencent.mobileqq.profilecard.observer.ProfileCardObserver -import io.ktor.client.request.header -import io.ktor.client.request.post -import io.ktor.client.request.setBody -import io.ktor.client.statement.bodyAsText -import io.ktor.http.ContentType.Application.Json -import io.ktor.http.contentType -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import moe.fuqiuluo.shamrock.tools.GlobalClient -import moe.fuqiuluo.shamrock.tools.json -import moe.fuqiuluo.shamrock.tools.slice -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import mqq.app.Packet -import tencent.im.oidb.cmd0x11b2.oidb_0x11b2 -import tencent.im.oidb.oidb_sso -import kotlin.coroutines.resume - -internal object CardSvc: BaseSvc() { - private val GetModelShowLock by lazy { - Mutex() - } - private val refreshCardLock by lazy { - Mutex() - } - - suspend fun getModelShow(uin: Long = app.longAccountUin): String { - return GetModelShowLock.withLock { - val uniPacket = UniPacket() - uniPacket.servantName = "VIP.CustomOnlineStatusServer.CustomOnlineStatusObj" - uniPacket.funcName = "GetCustomOnlineStatus" - val getCustomOnlineStatusReq = GetCustomOnlineStatusReq() - getCustomOnlineStatusReq.lUin = uin - getCustomOnlineStatusReq.sIMei = "" - uniPacket.put("req", getCustomOnlineStatusReq) - - val resp = sendBufferAW("VipCustom.GetCustomOnlineStatus", false, uniPacket.encode()) - ?: error("unable to fetch contact model_show") - Packet.decodePacket(resp, "rsp", GetCustomOnlineStatusRsp()).sBuffer - } - } - - suspend fun setModelShow(model: String, manu: String, modelShow: String, imei: String, show: Boolean) { - val cookie = TicketSvc.getCookie("vip.qq.com") - val csrf = TicketSvc.getCSRF(TicketSvc.getUin(), "vip.qq.com") - val p4token = TicketSvc.getPt4Token(TicketSvc.getUin(), "vip.qq.com") ?: "" - GlobalClient.post("https://club.vip.qq.com/srf-cgi-node?srfname=VIP.CustomOnlineStatusServer.CustomOnlineStatusObj.SetCustomOnlineStatus&ts=${System.currentTimeMillis()}&daid=18&g_tk=$csrf&pt4_token=$p4token") { - header("Cookie", cookie) - contentType(Json) - setBody(mapOf( - "servicesName" to "VIP.CustomOnlineStatusServer.CustomOnlineStatusObj", - "cmd" to "SetCustomOnlineStatus", - "args" to listOf(mapOf( - "sIMei" to imei, - "sModel" to model, - "sManu" to manu, - "lUin" to app.currentUin.toLong(), - "bShowInfo" to show, - "sModelShow" to modelShow - )) - ).json.toString()) - }.bodyAsText().let { - LogCenter.log({ "setModelShow() => $it" }, Level.DEBUG) - } - } - - suspend fun getSharePrivateArkMsg(peerId: Long): String { - val reqBody = oidb_0x11b2.BusinessCardV3Req() - reqBody.uin.set(peerId) - reqBody.jump_url.set("mqqapi://card/show_pslcard?src_type=internal&source=sharecard&version=1&uin=$peerId") - - val buffer = sendOidbAW("OidbSvcTrpcTcp.0x11ca_0", 4790, 0, reqBody.toByteArray()) - ?: error("unable to fetch contact ark_json_text") - val body = oidb_sso.OIDBSSOPkg() - body.mergeFrom(buffer.slice(4)) - val rsp = oidb_0x11b2.BusinessCardV3Rsp() - rsp.mergeFrom(body.bytes_bodybuffer.get().toByteArray()) - return rsp.signed_ark_msg.get() - } - - suspend fun getProfileCard(uin: Long): Result { - return getProfileCardFromCache(uin).onFailure { - return refreshAndGetProfileCard(uin) - } - } - - fun getProfileCardFromCache(uin: Long): Result { - val profileDataService = app - .getRuntimeService(IProfileDataService::class.java, "all") - val card = profileDataService.getProfileCard(uin.toString(), true) - return if (card == null || card.strNick.isNullOrEmpty()) { - Result.failure(Exception("unable to fetch profile card")) - } else { - Result.success(card) - } - } - - suspend fun refreshAndGetProfileCard(uin: Long): Result { - val dataService = app - .getRuntimeService(IProfileDataService::class.java, "all") - val card = refreshCardLock.withLock { - suspendCancellableCoroutine { - app.addObserver(object: ProfileCardObserver() { - override fun onGetProfileCard(success: Boolean, obj: Any) { - app.removeObserver(this) - if (!success || obj !is Card) { - it.resume(null) - } else { - dataService.saveProfileCard(obj) - it.resume(obj) - } - } - }) - app.getRuntimeService(IProfileProtocolService::class.java, "all") - .requestProfileCard(app.currentUin, uin.toString(), 12, 0L, 0.toByte(), 0L, 0L, null, "", 0L, 10004, null, 0.toByte()) - } - } - return if (card == null || card.strNick.isNullOrEmpty()) { - Result.failure(Exception("unable to fetch profile card")) - } else { - Result.success(card) - } - } - -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ChatSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ChatSvc.kt deleted file mode 100644 index b839ef90..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ChatSvc.kt +++ /dev/null @@ -1,20 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet - -import kotlinx.serialization.encodeToByteArray -import protobuf.auto.toByteArray - -import protobuf.oidb.cmd0x9082.Oidb0x9082 - -internal object ChatSvc: BaseSvc() { - fun setGroupMessageCommentFace(peer: Long, msgSeq: ULong, faceIndex: String, isSet: Boolean) { - val serviceId = if (isSet) 1 else 2 - sendOidb("OidbSvcTrpcTcp.0x9082_$serviceId", 36994, serviceId, Oidb0x9082( - peer = peer.toULong(), - msgSeq = msgSeq, - faceIndex = faceIndex, - flag = 1u, - u1 = 0u, - u2 = 0u - ).toByteArray()) - } -} diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/FileSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/FileSvc.kt deleted file mode 100644 index dfebd803..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/FileSvc.kt +++ /dev/null @@ -1,248 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet - -import com.tencent.mobileqq.pb.ByteStringMicro -import moe.fuqiuluo.qqinterface.servlet.structures.* -import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY -import moe.fuqiuluo.shamrock.tools.slice -import moe.fuqiuluo.shamrock.tools.toHexString -import moe.fuqiuluo.shamrock.utils.DeflateTools -import moe.fuqiuluo.symbols.decodeProtobuf -import protobuf.oidb.cmd0x6d7.CreateFolderReq -import protobuf.oidb.cmd0x6d7.DeleteFolderReq -import protobuf.oidb.cmd0x6d7.MoveFolderReq -import protobuf.oidb.cmd0x6d7.Oidb0x6d7ReqBody -import protobuf.oidb.cmd0x6d7.Oidb0x6d7RespBody -import protobuf.oidb.cmd0x6d7.RenameFolderReq -import tencent.im.oidb.cmd0x6d6.oidb_0x6d6 -import tencent.im.oidb.cmd0x6d8.oidb_0x6d8 -import tencent.im.oidb.oidb_sso -import protobuf.group_file_common.FolderInfo as GroupFileCommonFolderInfo -import protobuf.auto.toByteArray - -internal object FileSvc: BaseSvc() { - suspend fun createFileFolder(groupId: Long, folderName: String, parentFolderId: String = "/"): Result { - val data = Oidb0x6d7ReqBody( - createFolder = CreateFolderReq( - groupCode = groupId.toULong(), - appId = 3u, - parentFolderId = parentFolderId, - folderName = folderName - ) - ).toByteArray() - val resultBuffer = sendOidbAW("OidbSvc.0x6d7_0", 1751, 0, data) - ?: return Result.failure(Exception("unable to fetch result")) - val oidbPkg = oidb_sso.OIDBSSOPkg() - oidbPkg.mergeFrom(resultBuffer.slice(4)) - val rsp = oidbPkg.bytes_bodybuffer.get() - .toByteArray() - .decodeProtobuf() - if (rsp.createFolder?.retCode != 0) { - return Result.failure(Exception("unable to create folder: ${rsp.createFolder?.retCode}")) - } - return Result.success(rsp.createFolder!!.folderInfo!!) - } - - suspend fun deleteGroupFolder(groupId: Long, folderUid: String): Boolean { - val buffer = sendOidbAW("OidbSvc.0x6d7_1", 1751, 1, Oidb0x6d7ReqBody( - deleteFolder = DeleteFolderReq( - groupCode = groupId.toULong(), - appId = 3u, - folderId = folderUid - ) - ).toByteArray()) ?: return false - val oidbPkg = oidb_sso.OIDBSSOPkg() - oidbPkg.mergeFrom(buffer.slice(4)) - val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf() - return rsp.deleteFolder?.retCode == 0 - } - - suspend fun moveGroupFolder(groupId: Long, folderUid: String, newParentFolderUid: String): Boolean { - val buffer = sendOidbAW("OidbSvc.0x6d7_2", 1751, 2, Oidb0x6d7ReqBody( - moveFolder = MoveFolderReq( - groupCode = groupId.toULong(), - appId = 3u, - folderId = folderUid, - parentFolderId = "/" - ) - ).toByteArray()) ?: return false - val oidbPkg = oidb_sso.OIDBSSOPkg() - oidbPkg.mergeFrom(buffer.slice(4)) - val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf() - return rsp.moveFolder?.retCode == 0 - } - - suspend fun renameFolder(groupId: Long, folderUid: String, name: String): Boolean { - val buffer = sendOidbAW("OidbSvc.0x6d7_3", 1751, 3, Oidb0x6d7ReqBody( - renameFolder = RenameFolderReq( - groupCode = groupId.toULong(), - appId = 3u, - folderId = folderUid, - folderName = name - ) - ).toByteArray()) ?: return false - val oidbPkg = oidb_sso.OIDBSSOPkg() - oidbPkg.mergeFrom(buffer.slice(4)) - val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf() - return rsp.renameFolder?.retCode == 0 - } - - suspend fun deleteGroupFile(groupId: Long, bizId: Int, fileUid: String): Boolean { - val oidb0x6d6ReqBody = oidb_0x6d6.ReqBody().apply { - delete_file_req.set(oidb_0x6d6.DeleteFileReqBody().apply { - uint64_group_code.set(groupId) - uint32_app_id.set(3) - uint32_bus_id.set(bizId) - str_parent_folder_id.set("/") - str_file_id.set(fileUid) - }) - } - val result = sendOidbAW("OidbSvc.0x6d6_3", 1750, 3, oidb0x6d6ReqBody.toByteArray()) - ?: return false - val oidbPkg = oidb_sso.OIDBSSOPkg() - oidbPkg.mergeFrom(result.slice(4)) - val rsp = oidb_0x6d6.RspBody().apply { - mergeFrom(oidbPkg.bytes_bodybuffer.get().toByteArray()) - } - return rsp.delete_file_rsp.int32_ret_code.get() == 0 - } - - suspend fun getGroupFileSystemInfo(groupId: Long): FileSystemInfo { - val rspGetFileCntBuffer = sendOidbAW("OidbSvc.0x6d8_1", 1752, 2, oidb_0x6d8.ReqBody().also { - it.group_file_cnt_req.set(oidb_0x6d8.GetFileCountReqBody().also { - it.uint64_group_code.set(groupId) - it.uint32_app_id.set(3) - it.uint32_bus_id.set(0) - }) - }.toByteArray()) - val fileCnt: Int - val limitCnt: Int - if (rspGetFileCntBuffer != null) { - oidb_0x6d8.RspBody().mergeFrom(oidb_sso.OIDBSSOPkg() - .mergeFrom(rspGetFileCntBuffer.slice(4)) - .bytes_bodybuffer.get() - .toByteArray() - ).group_file_cnt_rsp.apply { - fileCnt = uint32_all_file_count.get() - limitCnt = uint32_limit_count.get() - } - } else { - throw RuntimeException("获取群文件数量失败") - } - - val rspGetFileSpaceBuffer = sendOidbAW("OidbSvc.0x6d8_1", 1752, 3, oidb_0x6d8.ReqBody().also { - it.group_space_req.set(oidb_0x6d8.GetSpaceReqBody().apply { - uint64_group_code.set(groupId) - uint32_app_id.set(3) - }) - }.toByteArray()) - val totalSpace: Long - val usedSpace: Long - if (rspGetFileSpaceBuffer != null) { - oidb_0x6d8.RspBody().mergeFrom(oidb_sso.OIDBSSOPkg() - .mergeFrom(rspGetFileSpaceBuffer.slice(4)) - .bytes_bodybuffer.get() - .toByteArray()).group_space_rsp.apply { - totalSpace = uint64_total_space.get() - usedSpace = uint64_used_space.get() - } - } else { - throw RuntimeException("获取群文件空间失败") - } - - return FileSystemInfo( - fileCnt, limitCnt, usedSpace, totalSpace - ) - } - - suspend fun getGroupRootFiles(groupId: Long): Result { - return getGroupFiles(groupId, "/") - } - - suspend fun getGroupFileInfo(groupId: Long, fileId: String, busid: Int): FileUrl { - return FileUrl(RichProtoSvc.getGroupFileDownUrl(groupId, fileId, busid)) - } - - suspend fun getGroupFiles(groupId: Long, folderId: String): Result { - val fileSystemInfo = getGroupFileSystemInfo(groupId) - val rspGetFileListBuffer = sendOidbAW("OidbSvc.0x6d8_1", 1752, 1, oidb_0x6d8.ReqBody().also { - it.file_list_info_req.set(oidb_0x6d8.GetFileListReqBody().apply { - uint64_group_code.set(groupId) - uint32_app_id.set(3) - str_folder_id.set(folderId) - - uint32_file_count.set(fileSystemInfo.fileCount) - uint32_all_file_count.set(0) - uint32_req_from.set(3) - uint32_sort_by.set(oidb_0x6d8.GetFileListReqBody.SORT_BY_TIMESTAMP) - - uint32_filter_code.set(0) - uint64_uin.set(0) - - uint32_start_index.set(0) - - bytes_context.set(ByteStringMicro.copyFrom(EMPTY_BYTE_ARRAY)) - - uint32_show_onlinedoc_folder.set(0) - }) - }.toByteArray(), timeout = 15_000L) - - return kotlin.runCatching { - val files = arrayListOf() - val dirs = arrayListOf() - if (rspGetFileListBuffer != null) { - val oidb = oidb_sso.OIDBSSOPkg().mergeFrom(rspGetFileListBuffer.slice(4).let { - if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it - }) - - oidb_0x6d8.RspBody().mergeFrom(oidb.bytes_bodybuffer.get().toByteArray()) - .file_list_info_rsp.apply { - rpt_item_list.get().forEach { file -> - if (file.uint32_type.get() == oidb_0x6d8.GetFileListRspBody.TYPE_FILE) { - val fileInfo = file.file_info - files.add(FileInfo( - groupId = groupId, - fileId = fileInfo.str_file_id.get(), - fileName = fileInfo.str_file_name.get(), - fileSize = fileInfo.uint64_file_size.get(), - busid = fileInfo.uint32_bus_id.get(), - uploadTime = fileInfo.uint32_upload_time.get(), - deadTime = fileInfo.uint32_dead_time.get(), - modifyTime = fileInfo.uint32_modify_time.get(), - downloadTimes = fileInfo.uint32_download_times.get(), - uploadUin = fileInfo.uint64_uploader_uin.get(), - uploadNick = fileInfo.str_uploader_name.get(), - md5 = fileInfo.bytes_md5.get().toByteArray().toHexString(), - sha = fileInfo.bytes_sha.get().toByteArray().toHexString(), - // 根本没有 - sha3 = fileInfo.bytes_sha3.get().toByteArray().toHexString(), - )) - } - else if (file.uint32_type.get() == oidb_0x6d8.GetFileListRspBody.TYPE_FOLDER) { - val folderInfo = file.folder_info - dirs.add(FolderInfo( - groupId = groupId, - folderId = folderInfo.str_folder_id.get(), - folderName = folderInfo.str_folder_name.get(), - totalFileCount = folderInfo.uint32_total_file_count.get(), - createTime = folderInfo.uint32_create_time.get(), - creator = folderInfo.uint64_create_uin.get(), - creatorNick = folderInfo.str_creator_name.get() - )) - } else { - LogCenter.log("未知文件类型: ${file.uint32_type.get()}", Level.WARN) - } - } - } - } else { - throw RuntimeException("获取群文件列表失败") - } - - GroupFileList(files, dirs) - }.onFailure { - LogCenter.log(it.message + ", buffer: ${rspGetFileListBuffer.toHexString()}", Level.ERROR) - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/FriendSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/FriendSvc.kt deleted file mode 100644 index 953239b8..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/FriendSvc.kt +++ /dev/null @@ -1,121 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) -@file:Suppress("IllegalIdentifier") -package moe.fuqiuluo.qqinterface.servlet - -import com.tencent.common.app.AppInterface -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.DelicateCoroutinesApi -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.suspendCancellableCoroutine -import moe.fuqiuluo.shamrock.tools.slice -import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher -import mqq.app.AppRuntime -import tencent.mobileim.structmsg.structmsg -import kotlin.coroutines.resume - -internal object FriendSvc: BaseSvc() { - - suspend fun getFriendList(refresh: Boolean): Result> { - val runtime = AppRuntimeFetcher.appRuntime - val service = runtime.getRuntimeService(IFriendDataService::class.java, "all") - if(refresh || !service.isInitFinished) { - if(!requestFriendList(runtime, service)) { - return Result.failure(Exception("获取好友列表失败")) - } - } - return Result.success(service.allFriends) - } - - // ProfileService.Pb.ReqSystemMsgAction.Friend - fun requestFriendRequest(msgSeq: Long, uin: Long, remark: String = "", approve: Boolean? = true, notSee: Boolean? = false) { - val app = AppRuntimeFetcher.appRuntime - if (app !is AppInterface) - throw RuntimeException("AppRuntime cannot cast to AppInterface") - 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? { - 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 respBuffer = sendBufferAW("ProfileService.Pb.ReqSystemMsgNew.Friend", true, req.toByteArray()) - return if (respBuffer == null) { - ArrayList() - } else { - try { - val msg = structmsg.RspSystemMsgNew() - msg.mergeFrom(respBuffer.slice(4)) - return msg.friendmsgs.get() - } catch (err: Throwable) { - requestFriendSystemMsgNew(msgNum, latestFriendSeq, latestGroupSeq, retryCnt - 1) - } - - } - } - - - private suspend fun requestFriendList(runtime: AppRuntime, dataService: IFriendDataService): Boolean { - val service = runtime.getRuntimeService(IFriendHandlerService::class.java, "all") - service.requestFriendList(true, 0) - return suspendCancellableCoroutine { continuation -> - val waiter = GlobalScope.launch { - while (!dataService.isInitFinished) { - delay(200) - } - continuation.resume(true) - } - continuation.invokeOnCancellation { - waiter.cancel() - continuation.resume(false) - } - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/GProSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/GProSvc.kt deleted file mode 100644 index daea9dc6..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/GProSvc.kt +++ /dev/null @@ -1,361 +0,0 @@ -@file:OptIn(ExperimentalSerializationApi::class) - -package moe.fuqiuluo.qqinterface.servlet - -import com.tencent.mobileqq.qqguildsdk.api.IGPSService -import com.tencent.qqnt.kernel.nativeinterface.GProGuildRole -import com.tencent.qqnt.kernel.nativeinterface.GProRoleCreateInfo -import com.tencent.qqnt.kernel.nativeinterface.GProRoleMemberList -import com.tencent.qqnt.kernel.nativeinterface.GProRolePermission -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.withTimeoutOrNull -import kotlinx.serialization.ExperimentalSerializationApi - -import moe.fuqiuluo.qqinterface.servlet.structures.GProChannelInfo -import moe.fuqiuluo.qqinterface.servlet.structures.GetGuildMemberListNextToken -import moe.fuqiuluo.qqinterface.servlet.structures.GuildInfo -import moe.fuqiuluo.qqinterface.servlet.structures.GuildStatus -import moe.fuqiuluo.qqinterface.servlet.structures.SlowModeInfo -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY -import moe.fuqiuluo.shamrock.tools.slice -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher -import moe.fuqiuluo.symbols.decodeProtobuf -import protobuf.auto.toByteArray -import protobuf.guild.GetGuildFeedsReq -import protobuf.guild.GetGuildFeedsRsp -import protobuf.oidb.cmd0xf88.GProFilter -import protobuf.oidb.cmd0xf88.GProUserInfo -import protobuf.oidb.cmd0xf88.Oidb0xf88Req -import protobuf.oidb.cmd0xf88.Oidb0xf88Rsp -import protobuf.oidb.cmx0xf57.Oidb0xf57Filter -import protobuf.oidb.cmx0xf57.Oidb0xf57GuildInfo -import protobuf.oidb.cmx0xf57.Oidb0xf57MetaInfo -import protobuf.oidb.cmx0xf57.Oidb0xf57Req -import protobuf.oidb.cmx0xf57.Oidb0xf57Rsp -import protobuf.oidb.cmx0xf57.Oidb0xf57U1 -import protobuf.oidb.cmx0xf57.Oidb0xf57U2 -import protobuf.qweb.DEFAULT_DEVICE_INFO -import protobuf.qweb.QWebExtInfo -import protobuf.qweb.QWebReq -import protobuf.qweb.QWebRsp -import tencent.im.oidb.oidb_sso -import kotlin.coroutines.resume - -internal object GProSvc: BaseSvc() { - fun getSelfTinyId(): ULong { - val service = app.getRuntimeService(IGPSService::class.java, "all") - return service.selfTinyId.toULong() - } - - suspend fun getGuildInfo(guildId: ULong): Result { - val respBuffer = sendOidbAW("OidbSvcTrpcTcp.0xf57_9", 0xf57, 9, Oidb0xf57Req( - filter = Oidb0xf57Filter( - u1 = Oidb0xf57U1(1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u), - u2 = Oidb0xf57U2(1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u) - ), - guildInfo = Oidb0xf57GuildInfo(guildId = guildId) - ).toByteArray()) - val body = oidb_sso.OIDBSSOPkg() - if (respBuffer == null) { - return Result.failure(Exception("unable to send packet")) - } - body.mergeFrom(respBuffer.slice(4)) - return runCatching { - body.bytes_bodybuffer.get() - .toByteArray() - .decodeProtobuf().metaInfo - } - } - - suspend fun getGuildFeeds(guildId: ULong, channelId: ULong, startIndex: Int): Result { - val buffer = sendBufferAW("QChannelSvr.trpc.qchannel.commreader.ComReader.GetGuildFeeds", true, QWebReq( - seq = 10, - qua = PlatformUtils.getQUA(), - deviceInfo = DEFAULT_DEVICE_INFO, - buffer = GetGuildFeedsReq( - count = 12, - from = startIndex, - feedAttchInfo = EMPTY_BYTE_ARRAY, - guildId = guildId, - getType = 1, - u7 = 0, - u8 = 1, - u9 = EMPTY_BYTE_ARRAY - ).toByteArray(), - traceId = app.account + "_0_0", - extinfo = listOf( - QWebExtInfo("fc-appid", "96"), - QWebExtInfo("environment_id", "shamrock"), - QWebExtInfo("tiny_id", getSelfTinyId().toString()), - ) - ).toByteArray()) ?: return Result.failure(Exception("unable to send packet")) - val webRsp = buffer.slice(4).decodeProtobuf() - if(webRsp.buffer == null) return Result.failure(Exception("server error")) - val wupBuffer = webRsp.buffer!! - val feeds = wupBuffer.decodeProtobuf() - return Result.success(feeds) - } - - fun getChannelList(guildId: ULong, refresh: Boolean = false): Result> { - if (refresh) { - refreshGuildInfo(guildId) - } - val result = arrayListOf() - app.getRuntimeService(IGPSService::class.java, "all").getChannelList(guildId.toString()).forEach { - result.add(GProChannelInfo( - ownerGuildId = guildId, - guildId = it.guildId, - channelId = it.channelUin.toLong(), - channelUin = it.channelUin.toLong(), - channelName = it.channelName ?: "", - channelType = it.type, - createTime = it.createTime, - creatorTinyId = it.creatorId.toLong(), - talkPermission = it.talkPermission, - visibleType = it.visibleType, - currentSlowMode = it.slowModeKey, - slowModes = it.gProSlowModeInfoList.map { - SlowModeInfo(it.slowModeKey, it.slowModeText, it.speakFrequency, it.slowModeCircle) - }, - appIconUrl = it.iconUrl, - jumpType = it.appChannelJumpType, - jumpSwitch = it.jumpSwitch, - jumpUrl = it.appChannelJumpUrl, - categoryId = it.categoryId, - myTalkPermission = it.myTalkPermissionType, - maxMemberCount = it.channelMemberMax - )) - } - return Result.success(result) - } - - fun refreshGuildInfo(guildId: ULong) { - val kernelGProService = NTServiceFetcher.kernelService.wrapperSession.guildService - kernelGProService.refreshGuildInfo(guildId.toLong(), true, 1) - } - - suspend fun getGuildMemberList( - guildId: ULong, - startIndex: Long = 0, - roleIndex: Long = 1, - count: Int = 50, - fetchAll: Boolean = false, - result: ArrayList = arrayListOf() - ): Result>> { - val kernelGProService = NTServiceFetcher.kernelService.wrapperSession.guildService - - val fetchGuildMemberListResult: Pair> = (withTimeoutOrNull(5000) { - suspendCancellableCoroutine { - kernelGProService.fetchMemberListWithRole(guildId.toLong(), 0, startIndex, roleIndex, count, 0) { code, reason, finish, nextIndex, nextRoleIdIndex, _, seq, roleList -> - if (code == 0) { - it.resume(GetGuildMemberListNextToken(nextIndex, nextRoleIdIndex, seq, finish) to roleList) - } else { - LogCenter.log("fetchMemberListWithRole failed: $code($reason)", Level.WARN) - it.resume(null) - } - } - } - }) ?: return Result.failure(Exception("unable to fetch guild member list")) - - val nextToken = fetchGuildMemberListResult.first - val roleList = fetchGuildMemberListResult.second - result.addAll(roleList) - return if (fetchAll) { - if (!fetchGuildMemberListResult.first.finish) { - getGuildMemberList(guildId, nextToken.startIndex, nextToken.roleIndex, count, true, result) - } else { - Result.success(nextToken.copy(finish = true) to result) - } - } else { - Result.success(nextToken to result) - } - } - - suspend fun getSelfGuildInfo(): Result { - val selfTinyId = getSelfTinyId() - return getUserGuildInfo(0u, selfTinyId) - } - - suspend fun getUserGuildInfo( - guildId: ULong, - memberTinyId: ULong - ): Result { - val respBuffer = sendOidbAW("OidbSvcTrpcTcp.0xf88_1", 0xf88, 1, Oidb0xf88Req( - filter = GProFilter(1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u), - memberId = 0uL, - tinyId = memberTinyId, - guildId = guildId - ).toByteArray()) - val body = oidb_sso.OIDBSSOPkg() - if (respBuffer == null) { - return Result.failure(Exception("unable to send packet")) - } - body.mergeFrom(respBuffer.slice(4)) - return runCatching { - body.bytes_bodybuffer.get().toByteArray().decodeProtobuf().userInfo!! - } - } - - private fun getGuildListByOldApi(result: ArrayList) { - app.getRuntimeService(IGPSService::class.java, "all").guildList?.forEach { - result.add(GuildInfo( - guildId = it.guildID.toLong(), - guildName = it.guildName ?: "", - guildDisplayId = it.guildNumber ?: "", - profile = it.profile ?: "", - status = GuildStatus( - isEnable = !it.isFrozen && !it.isBanned, - isBanned = it.isBanned, - isFrozen = it.isFrozen - ), - ownerId = 0, - shutUpTime = it.shutUpExpireTime, - allowSearch = it.allowSearch - )) - } - } - - private fun getGuildListByNt(result: ArrayList) { - val kernelGProService = NTServiceFetcher.kernelService.wrapperSession.guildService - kernelGProService.guildListFromCache.forEach { - if (it.result != 0) return@forEach - val guildInfo = it.guildInfo - result.add(GuildInfo( - guildId = it.guildId, - guildName = guildInfo.guildName ?: "", - guildDisplayId = guildInfo.guildNumber ?: "", - profile = guildInfo.profile ?: "", - status = GuildStatus( - isEnable = guildInfo.guildStatus?.isEnable == 1, - isBanned = guildInfo.guildStatus?.isBanned == 1, - isFrozen = guildInfo.guildStatus?.isFrozen == 1 - ), - ownerId = guildInfo.ownerTinyid, - shutUpTime = guildInfo.shutupExpireTime, - allowSearch = guildInfo.allowSearch == 1 - )) - } - } - - suspend fun fetchGuildMemberRoles(guildId: ULong, tinyId: ULong, refresh: Boolean = false): Result> { - val kernelGProService = NTServiceFetcher.kernelService.wrapperSession.guildService - if (refresh) { - kernelGProService.refreshGuildUserProfileInfo(guildId.toLong(), tinyId.toLong(), 1) - } - val result: ArrayList = withTimeoutOrNull(5000) { - suspendCancellableCoroutine { - kernelGProService.fetchMemberRoles(guildId.toLong(), 0, tinyId.toLong(), 2) { code, reason, roles -> - it.resume(roles) - } - } - } ?: return Result.failure(Exception("unable to fetch guild member roles")) - return Result.success(result) - } - - fun getGuildList(refresh: Boolean = false, forceOldApi: Boolean): ArrayList { - val kernelGProService = NTServiceFetcher.kernelService.wrapperSession.guildService - if (refresh) { - kernelGProService.refreshGuildList(true) - kernelGProService.guildListFromCache.forEach { - refreshGuildInfo(it.guildId.toULong()) - } - } - val result = arrayListOf() - if (PlatformUtils.getQQVersionCode() < PlatformUtils.QQ_9_0_8_VER || forceOldApi) { - getGuildListByOldApi(result) - } else { - runCatching { - getGuildListByNt(result) - }.onFailure { - LogCenter.log("GetGuildListByNt failed: ${it.stackTraceToString()}", Level.ERROR) - getGuildListByOldApi(result) // 防止QQ更新API导致无法获取 - } - } - - return result - } - - suspend fun getGuildRoles(guildId: ULong): Result> { - val kernelGProService = NTServiceFetcher.kernelService.wrapperSession.guildService - val roles: List = withTimeoutOrNull(5000) { - suspendCancellableCoroutine { - kernelGProService.fetchRoleListWithPermission(guildId.toLong(), 1) { code, _, roles, _, _, _ -> - if (code != 0) it.resume(null) else it.resume(roles) - } - } - } ?: return Result.failure(Exception("unable to fetch guild roles")) - return Result.success(roles) - } - - fun deleteGuildRole(guildId: ULong, roleId: ULong) { - val kernelGProService = NTServiceFetcher.kernelService.wrapperSession.guildService - kernelGProService.deleteRole(guildId.toLong(), roleId.toLong()) { code, msg, result -> - if (code != 0) { - LogCenter.log("deleteGuildRole failed: $code($msg) => $result", Level.WARN) - } - } - } - - fun setMemberRole(guildId: ULong, tinyId: ULong, roleId: ULong, isSet: Boolean) { - val kernelGProService = NTServiceFetcher.kernelService.wrapperSession.guildService - val addList = arrayListOf() - val rmList = arrayListOf() - (if (isSet) addList else rmList).add(roleId.toLong()) - kernelGProService.setMemberRoles(guildId.toLong(), 0, 0, tinyId.toLong(), addList, rmList) { code, msg, result -> - if (code != 0) { - LogCenter.log("setMemberRole failed: $code($msg) => $result", Level.WARN) - } - } - } - - suspend fun getGuildRolePermission(guildId: ULong, roleId: ULong): Result { - val kernelGProService = NTServiceFetcher.kernelService.wrapperSession.guildService - val role:GProGuildRole = withTimeoutOrNull(5000) { - suspendCancellableCoroutine { - kernelGProService.fetchRoleWithPermission(guildId.toLong(), roleId.toLong(), 1) { code, msg, role, _, _, _ -> - if (code != 0) { - LogCenter.log("getGuildRolePermission failed: $code($msg)", Level.WARN) - it.resume(null) - } else it.resume(role) - } - } - } ?: return Result.failure(Exception("unable to fetch guild role permission")) - return Result.success(role) - } - - suspend fun updateGuildRole(guildId: ULong, roleId: ULong, name: String, color: Long): Result { - val oldInfo = getGuildRolePermission(guildId, roleId).onFailure { - return Result.failure(it) - }.getOrThrow() - val kernelGProService = NTServiceFetcher.kernelService.wrapperSession.guildService - val info = GProRoleCreateInfo( - name, color, oldInfo.bHoist, oldInfo.rolePermissions - ) - kernelGProService.setRoleInfo(guildId.toLong(), roleId.toLong(), info) { code, msg, result -> - if (code != 0) { - LogCenter.log("updateGuildRole failed: $code($msg) => $result", Level.WARN) - } - } - return Result.success(Unit) - } - - suspend fun createGuildRole(guildId: ULong, name: String, color: Long, initialUsers: ArrayList): Result { - val kernelGProService = NTServiceFetcher.kernelService.wrapperSession.guildService - val permission = GProRolePermission(false, arrayListOf()) - val info = GProRoleCreateInfo(name, color, false, permission) - val role: GProGuildRole = withTimeoutOrNull(5000) { - suspendCancellableCoroutine { - kernelGProService.createRole(guildId.toLong(), info, initialUsers) { code, msg, result, role -> - if (code != 0) { - LogCenter.log("createGuildRole failed: $code($msg) => $result", Level.WARN) - it.resume(null) - } else it.resume(role) - } - } - } ?: return Result.failure(Exception("unable to create guild role")) - return Result.success(role) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/GroupSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/GroupSvc.kt deleted file mode 100644 index 4770f740..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/GroupSvc.kt +++ /dev/null @@ -1,1152 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) - -package moe.fuqiuluo.qqinterface.servlet - -import KQQ.RespBatchProcess -import androidx.core.text.HtmlCompat -import com.qq.jce.wup.UniPacket -import com.tencent.common.app.AppInterface -import com.tencent.mobileqq.app.BusinessHandlerFactory -import com.tencent.mobileqq.app.QQAppInterface -import com.tencent.mobileqq.data.troop.TroopInfo -import com.tencent.mobileqq.data.troop.TroopMemberInfo -import com.tencent.mobileqq.pb.ByteStringMicro -import com.tencent.mobileqq.troop.api.ITroopInfoService -import com.tencent.mobileqq.troop.api.ITroopMemberInfoService -import com.tencent.protofile.join_group_link.join_group_link -import com.tencent.qphone.base.remote.ToServiceMsg -import com.tencent.qqnt.kernel.nativeinterface.MemberInfo -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import friendlist.stUinInfo -import io.ktor.client.call.body -import io.ktor.client.request.forms.MultiPartFormDataContent -import io.ktor.client.request.forms.formData -import io.ktor.client.request.forms.submitForm -import io.ktor.client.request.get -import io.ktor.client.request.header -import io.ktor.client.request.post -import io.ktor.client.request.setBody -import io.ktor.http.ContentType -import io.ktor.http.Headers -import io.ktor.http.HttpHeaders -import io.ktor.http.contentType -import io.ktor.http.headers -import io.ktor.http.parameters -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import kotlinx.coroutines.withTimeoutOrNull -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.encodeToByteArray -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.decodeFromStream -import kotlinx.serialization.json.jsonObject - -import moe.fuqiuluo.qqinterface.servlet.TicketSvc.getLongUin -import moe.fuqiuluo.qqinterface.servlet.TicketSvc.getUin -import moe.fuqiuluo.qqinterface.servlet.structures.GroupAtAllRemainInfo -import moe.fuqiuluo.qqinterface.servlet.structures.NotJoinedGroupInfo -import moe.fuqiuluo.qqinterface.servlet.structures.ProhibitedMemberInfo -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.remote.service.data.EssenceMessage -import moe.fuqiuluo.shamrock.remote.service.data.GroupAnnouncement -import moe.fuqiuluo.shamrock.remote.service.data.GroupAnnouncementMessage -import moe.fuqiuluo.shamrock.remote.service.data.GroupAnnouncementMessageImage -import moe.fuqiuluo.shamrock.remote.service.data.push.MemberRole -import moe.fuqiuluo.shamrock.tools.EmptyJsonArray -import moe.fuqiuluo.shamrock.tools.GlobalClient -import moe.fuqiuluo.shamrock.tools.asInt -import moe.fuqiuluo.shamrock.tools.asJsonArrayOrNull -import moe.fuqiuluo.shamrock.tools.asJsonObject -import moe.fuqiuluo.shamrock.tools.asLong -import moe.fuqiuluo.shamrock.tools.asString -import moe.fuqiuluo.shamrock.tools.asStringOrNull -import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty -import moe.fuqiuluo.shamrock.tools.putBuf32Long -import moe.fuqiuluo.shamrock.tools.slice -import moe.fuqiuluo.shamrock.utils.FileUtils -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher -import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher -import protobuf.oidb.cmd0xf16.Oidb0xf16 -import protobuf.oidb.cmd0xf16.SetGroupRemarkReq -import mqq.app.MobileQQ -import protobuf.auto.toByteArray -import tencent.im.group.group_member_info -import tencent.im.oidb.cmd0x88d.oidb_0x88d -import tencent.im.oidb.cmd0x899.oidb_0x899 -import tencent.im.oidb.cmd0x89a.oidb_0x89a -import tencent.im.oidb.cmd0x8a0.oidb_0x8a0 -import tencent.im.oidb.cmd0x8a7.cmd0x8a7 -import tencent.im.oidb.cmd0x8fc.Oidb_0x8fc -import tencent.im.oidb.cmd0xeac.oidb_0xeac -import tencent.im.oidb.cmd0xeb7.oidb_0xeb7 -import tencent.im.oidb.cmd0xed3.oidb_cmd0xed3 -import tencent.im.oidb.oidb_sso -import tencent.im.troop.honor.troop_honor -import tencent.mobileim.structmsg.structmsg -import java.lang.reflect.Method -import java.lang.reflect.Modifier -import java.nio.ByteBuffer -import kotlin.coroutines.resume - -internal object GroupSvc: BaseSvc() { - private const val GET_MEMBER_ROLE_BY_NT = false - - private val RefreshTroopMemberInfoLock by lazy { - Mutex() - } - private val RefreshTroopMemberListLock by lazy { - Mutex() - } - - private lateinit var METHOD_REQ_MEMBER_INFO: Method - private lateinit var METHOD_REQ_MEMBER_INFO_V2: Method - private lateinit var METHOD_REQ_TROOP_LIST: Method - private lateinit var METHOD_REQ_TROOP_MEM_LIST: Method - private lateinit var METHOD_REQ_MODIFY_GROUP_NAME: Method - - suspend fun getGroupRemainAtAllRemain (groupId: Long): Result { - val buffer = sendOidbAW("OidbSvcTrpcTcp.0x8a7_0", 2215, 0, cmd0x8a7.ReqBody().apply { - uint32_sub_cmd.set(1) - uint32_limit_interval_type_for_uin.set(2) - uint32_limit_interval_type_for_group.set(1) - uint64_uin.set(getLongUin()) - uint64_group_code.set(groupId) - }.toByteArray(), trpc = true) ?: return Result.failure(RuntimeException("[oidb] timeout")) - val body = oidb_sso.OIDBSSOPkg() - body.mergeFrom(buffer.slice(4)) - if(body.uint32_result.get() != 0) { - return Result.failure(RuntimeException(body.str_error_msg.get())) - } - - val resp = cmd0x8a7.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray()) - return Result.success(GroupAtAllRemainInfo( - canAtAll = resp.bool_can_at_all.get(), - remainAtAllCountForGroup = resp.uint32_remain_at_all_count_for_group.get(), - remainAtAllCountForUin = resp.uint32_remain_at_all_count_for_uin.get() - )) - } - suspend fun getProhibitedMemberList(groupId: Long): Result> { - val buffer = sendOidbAW("OidbSvc.0x899_0", 2201, 0, oidb_0x899.ReqBody().apply { - uint64_group_code.set(groupId) - uint64_start_uin.set(0) - uint32_identify_flag.set(6) - memberlist_opt.set(oidb_0x899.memberlist().apply { - uint64_member_uin.set(0) - uint32_shutup_timestap.set(0) - }) - }.toByteArray()) ?: return Result.failure(RuntimeException("[oidb] timeout")) - val body = oidb_sso.OIDBSSOPkg() - body.mergeFrom(buffer.slice(4)) - if(body.uint32_result.get() != 0) { - return Result.failure(RuntimeException(body.str_error_msg.get())) - } - - val resp = oidb_0x899.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray()) - return Result.success(resp.rpt_memberlist.get().map { - ProhibitedMemberInfo(it.uint64_member_uin.get(), it.uint32_shutup_timestap.get()) - }) - } - - fun poke(groupId: Long, userId: Long) { - val req = oidb_cmd0xed3.ReqBody().apply { - uint64_group_code.set(groupId) - uint64_to_uin.set(userId) - uint32_msg_seq.set(0) - } - sendOidb("OidbSvc.0xed3", 3795, 1, req.toByteArray()) - } - - suspend fun getGroupMemberList(groupId: Long, refresh: Boolean): Result> { - val service = app.getRuntimeService(ITroopMemberInfoService::class.java, "all") - var memberList = service.getAllTroopMembers(groupId.toString()) - if (refresh || memberList == null) { - memberList = requestTroopMemberInfo(service, groupId).onFailure { - return Result.failure(Exception("获取群成员列表失败")) - }.getOrThrow() - } - - getGroupInfo(groupId, true).onSuccess { - if(it.wMemberNum > memberList.size) { - return getGroupMemberList(groupId, true) - } - } - - return Result.success(memberList) - } - - suspend fun getGroupList(refresh: Boolean): Result> { - val service = app.getRuntimeService(ITroopInfoService::class.java, "all") - - var troopList = service.allTroopList - if(refresh || !service.isTroopCacheInited || troopList == null) { - if(!requestGroupInfo(service)) { - return Result.failure(Exception("获取群列表失败")) - } else { - troopList = service.allTroopList - } - } - return Result.success(troopList) - } - - suspend fun getNotJoinedGroupInfo(groupId: Long): Result { - return withTimeoutOrNull(5000) timeout@{ - val toServiceMsg = createToServiceMsg("ProfileService.ReqBatchProcess") - toServiceMsg.extraData.putLong("troop_code", groupId) - toServiceMsg.extraData.putBoolean("is_admin", false) - toServiceMsg.extraData.putInt("from", 0) - val buffer = sendAW(toServiceMsg) - val uniPacket = UniPacket(true) - uniPacket.encodeName = "utf-8" - uniPacket.decode(buffer) - val respBatchProcess = uniPacket.getByClass("RespBatchProcess", RespBatchProcess()) - val batchRespInfo = oidb_0x88d.RspBody().mergeFrom(oidb_sso.OIDBSSOPkg() - .mergeFrom(respBatchProcess.batch_response_list.first().buffer) - .bytes_bodybuffer.get().toByteArray()).stzrspgroupinfo.get().firstOrNull() - ?: return@timeout Result.failure(Exception("获取群信息失败")) - val info = batchRespInfo.stgroupinfo - Result.success(NotJoinedGroupInfo( - groupId = batchRespInfo.uint64_group_code.get(), - maxMember = info.uint32_group_member_max_num.get(), - memberCount = info.uint32_group_member_num.get(), - groupName = info.string_group_name.get().toStringUtf8(), - groupDesc = info.string_group_finger_memo.get().toStringUtf8(), - owner = info.uint64_group_owner.get(), - createTime = info.uint32_group_create_time.get().toLong(), - groupFlag = info.uint32_group_flag.get(), - groupFlagExt = info.uint32_group_flag_ext.get() - )) - } ?: Result.failure(Exception("获取群信息超时")) - } - - suspend fun getGroupInfo(groupId: Long, refresh: Boolean): Result { - val service = app - .getRuntimeService(ITroopInfoService::class.java, "all") - - val groupInfo = getGroupInfo(groupId) - - return if(refresh || !service.isTroopCacheInited || groupInfo.troopuin.isNullOrBlank()) { - requestGroupInfo(service, groupId) - } else { - Result.success(groupInfo) - } - - } - - suspend fun setGroupUniqueTitle(groupId: Long, userId: Long, title: String) { - val localMemberInfo = getTroopMemberInfoByUin(groupId, userId, true).getOrThrow() - val req = Oidb_0x8fc.ReqBody() - req.uint64_group_code.set(groupId) - val memberInfo = Oidb_0x8fc.MemberInfo() - memberInfo.uint64_uin.set(userId) - memberInfo.bytes_uin_name.set(ByteStringMicro.copyFromUtf8(localMemberInfo.troopnick.ifEmpty { - localMemberInfo.troopremark.ifNullOrEmpty("") - })) - memberInfo.bytes_special_title.set(ByteStringMicro.copyFromUtf8(title)) - memberInfo.uint32_special_title_expire_time.set(-1) - req.rpt_mem_level_info.add(memberInfo) - sendOidb("OidbSvc.0x8fc_2", 2300, 2, req.toByteArray()) - } - - fun modifyGroupMemberCard(groupId: Long, userId: Long, name: String): Boolean { - val createToServiceMsg: ToServiceMsg = createToServiceMsg("friendlist.ModifyGroupCardReq") - createToServiceMsg.extraData.putLong("dwZero", 0L) - createToServiceMsg.extraData.putLong("dwGroupCode", groupId) - val info = stUinInfo() - info.cGender = -1 - info.dwuin = userId - info.sEmail = "" - info.sName = name - info.sPhone = "" - info.sRemark = "" - info.dwFlag = 1 - createToServiceMsg.extraData.putSerializable("vecUinInfo", arrayListOf(info)) - createToServiceMsg.extraData.putLong("dwNewSeq", 0L) - send(createToServiceMsg) - return true - } - - fun modifyGroupRemark(groupId: Long, remark: String): Boolean { - sendOidb("OidbSvc.0xf16_1", 3862, 1, Oidb0xf16( - setGroupRemarkReq = SetGroupRemarkReq( - groupCode = groupId.toULong(), - groupUin = groupCode2GroupUin(groupId).toULong(), - groupRemark = remark - ) - ).toByteArray()) - return true - } - - suspend fun setEssenceMessage(groupId: Long, seq: Long, rand: Long): Pair { - val buffer = sendOidbAW("OidbSvc.0xeac_1", 3756, 1, oidb_0xeac.ReqBody().apply { - group_code.set(groupId) - msg_seq.set(seq.toInt()) - msg_random.set(rand.toInt()) - }.toByteArray()) ?: return Pair(false, "unknown error") - val body = oidb_sso.OIDBSSOPkg() - body.mergeFrom(buffer.slice(4)) - val result = oidb_0xeac.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray()) - return if (result.wording.has()) { - LogCenter.log("设置群精华失败: ${result.wording.get()}") - Pair(false, "设置群精华失败: ${result.wording.get()}") - } else { - LogCenter.log("设置群精华 -> $groupId: $seq") - Pair(true, "ok") - } - } - - suspend fun deleteEssenceMessage(groupId: Long, seq: Long, rand: Long): Pair { - val buffer = sendOidbAW("OidbSvc.0xeac_2", 3756, 2, oidb_0xeac.ReqBody().apply { - group_code.set(groupId) - msg_seq.set(seq.toInt()) - msg_random.set(rand.toInt()) - }.toByteArray()) - val body = oidb_sso.OIDBSSOPkg() - if (buffer == null) { - return Pair(false, "unknown error") - } - body.mergeFrom(buffer.slice(4)) - val result = oidb_0xeac.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray()) - return if (result.wording.has()) { - LogCenter.log("移除群精华失败: ${result.wording.get()}") - Pair(false, "移除群精华失败: ${result.wording.get()}") - } else { - LogCenter.log("移除群精华 -> $groupId: $seq") - Pair(true, "ok") - } - } - - fun setGroupAdmin(groupId: Long, userId: Long, enable: Boolean) { - val buffer = ByteBuffer.allocate(9) - buffer.putBuf32Long(groupId) - buffer.putBuf32Long(userId) - buffer.put(if (enable) 1 else 0) - val array = buffer.array() - sendOidb("OidbSvc.0x55c_1", 1372, 1, array) - } - - fun setGroupWholeBan(groupId: Long, enable: Boolean) { - val reqBody = oidb_0x89a.ReqBody() - reqBody.uint64_group_code.set(groupId) - reqBody.st_group_info.set(oidb_0x89a.groupinfo().apply { - uint32_shutup_time.set(if (enable) 268435455 else 0) - }) - sendOidb("OidbSvc.0x89a_0", 2202, 0, reqBody.toByteArray()) - } - - fun banMember(groupId: Long, memberUin: Long, time: Int) { - val buffer = ByteBuffer.allocate(1 * 8 + 7) - buffer.putBuf32Long(groupId) - buffer.put(32.toByte()) - buffer.putShort(1) - buffer.putBuf32Long(memberUin) - buffer.putInt(time) - val array = buffer.array() - sendOidb("OidbSvc.0x570_8", 1392, 8, array) - } - - fun kickMember(groupId: Long, rejectAddRequest: Boolean, kickMsg: String, vararg memberUin: Long) { - val reqBody = oidb_0x8a0.ReqBody() - reqBody.opt_uint64_group_code.set(groupId) - - memberUin.forEach { - val memberInfo = oidb_0x8a0.KickMemberInfo() - memberInfo.opt_uint32_operate.set(5) - memberInfo.opt_uint64_member_uin.set(it) - memberInfo.opt_uint32_flag.set(if (rejectAddRequest) 1 else 0) - reqBody.rpt_msg_kick_list.add(memberInfo) - } - if (kickMsg.isNotEmpty()) { - reqBody.bytes_kick_msg.set(ByteStringMicro.copyFrom(kickMsg.toByteArray())) - } - - sendOidb("OidbSvc.0x8a0_0", 2208, 0, reqBody.toByteArray()) - } - - fun getGroupInfo(groupId: Long): TroopInfo { - val runtime = AppRuntimeFetcher.appRuntime as QQAppInterface - - val service = runtime - .getRuntimeService(ITroopInfoService::class.java, "all") - - return service.getTroopInfo(groupId.toString()) - } - - fun getAdminList( - groupId: Long, - withOwner: Boolean = false - ): List { - val groupInfo = getGroupInfo(groupId) - return (groupInfo.Administrator ?: "") - .split("|", ",") - .also { - if (withOwner && it is ArrayList) { - it.add(0, groupInfo.troopowneruin) - } - } - .map { (it.ifNullOrEmpty("0")!!).toLong() } - .filter { it != 0L } - } - - suspend fun getMemberRole(groupId: Long, memberUin: Long): MemberRole { - if (!GET_MEMBER_ROLE_BY_NT) { - return when (memberUin) { - getOwner(groupId) -> MemberRole.Owner - in getAdminList(groupId) -> MemberRole.Admin - else -> MemberRole.Member - } - } - return when(getTroopMemberInfoByUinViaNt(groupId, memberUin, 3000).getOrNull()?.role) { - com.tencent.qqnt.kernel.nativeinterface.MemberRole.STRANGER -> MemberRole.Stranger - com.tencent.qqnt.kernel.nativeinterface.MemberRole.MEMBER -> MemberRole.Member - com.tencent.qqnt.kernel.nativeinterface.MemberRole.ADMIN -> MemberRole.Admin - com.tencent.qqnt.kernel.nativeinterface.MemberRole.OWNER -> MemberRole.Owner - com.tencent.qqnt.kernel.nativeinterface.MemberRole.UNSPECIFIED, null -> when (memberUin) { - getOwner(groupId) -> MemberRole.Owner - in getAdminList(groupId) -> MemberRole.Admin - else -> MemberRole.Member - } - } - } - - fun getOwner(groupId: Long): Long { - val groupInfo = getGroupInfo(groupId) - return groupInfo.troopowneruin?.toLong() ?: 0 - } - - fun isOwner(groupId: Long): Boolean { - val groupInfo = getGroupInfo(groupId) - return groupInfo.troopowneruin == app.account - } - - fun isAdmin(groupId: Long): Boolean { - val service = app - .getRuntimeService(ITroopInfoService::class.java, "all") - - val groupInfo = service.getTroopInfo(groupId.toString()) - - return groupInfo.isAdmin || groupInfo.troopowneruin == app.account - } - - fun resignTroop(groupId: Long) { - sendExtra("ProfileService.GroupMngReq") { - it.putInt("groupreqtype", 2) - it.putString("troop_uin", groupId.toString()) - it.putString("uin", currentUin) - } - } - - fun modifyTroopName(groupId: Long, name: String) { - val businessHandler = app.getBusinessHandler(BusinessHandlerFactory.TROOP_MODIFY_HANDLER) - - if (!GroupSvc::METHOD_REQ_MODIFY_GROUP_NAME.isInitialized) { - METHOD_REQ_MODIFY_GROUP_NAME = businessHandler.javaClass.declaredMethods.first { - it.parameterCount == 3 - && it.parameterTypes[0] == String::class.java - && it.parameterTypes[1] == String::class.java - && it.parameterTypes[2] == Boolean::class.java - && !Modifier.isPrivate(it.modifiers) - } - } - - METHOD_REQ_MODIFY_GROUP_NAME.invoke(businessHandler, groupId.toString(), name, false) - } - - fun parseHonor(honor: String?): List { - return (honor ?: "") - .split("|") - .filter { it.isNotBlank() } - .map { it.toInt() } - } - - fun groupUin2GroupCode(groupuin: Long): Long { - var calc = groupuin / 1000000L - while (true) { - calc -= if (calc >= 0 + 202 && calc + 202 <= 10) { - (202 - 0).toLong() - } else if (calc >= 11 + 480 && calc <= 19 + 480) { - (480 - 11).toLong() - } else if (calc >= 20 + 2100 && calc <= 66 + 2100) { - (2100 - 20).toLong() - } else if (calc >= 67 + 2010 && calc <= 156 + 2010) { - (2010 - 67).toLong() - } else if (calc >= 157 + 2147 && calc <= 209 + 2147) { - (2147 - 157).toLong() - } else if (calc >= 210 + 4100 && calc <= 309 + 4100) { - (4100 - 210).toLong() - } else if (calc >= 310 + 3800 && calc <= 499 + 3800) { - (3800 - 310).toLong() - } else { - break - } - } - return calc * 1000000L + groupuin % 1000000L - } - - fun groupCode2GroupUin(groupcode: Long): Long { - var calc = groupcode / 1000000L - loop@ while (true) calc += when (calc) { - in 0..10 -> { - (202 - 0).toLong() - } - in 11..19 -> { - (480 - 11).toLong() - } - in 20..66 -> { - (2100 - 20).toLong() - } - in 67..156 -> { - (2010 - 67).toLong() - } - in 157..209 -> { - (2147 - 157).toLong() - } - in 210..309 -> { - (4100 - 210).toLong() - } - in 310..499 -> { - (3800 - 310).toLong() - } - else -> { - break@loop - } - } - return calc * 1000000L + groupcode % 1000000L - } - - suspend fun getShareTroopArkMsg(groupId: Long): String { - val reqBody = join_group_link.ReqBody() - reqBody.get_ark.set(true) - reqBody.type.set(1) - reqBody.group_code.set(groupId) - val buffer = sendBufferAW("GroupSvc.JoinGroupLink", true, reqBody.toByteArray()) - ?: error("unable to fetch contact ark_json_text") - val body = join_group_link.RspBody() - body.mergeFrom(buffer.slice(4)) - return body.signed_ark.get().toStringUtf8() - } - - suspend fun getTroopMemberInfoByUin( - groupId: Long, - uin: Long, - refresh: Boolean = false - ): Result { - val service = app.getRuntimeService(ITroopMemberInfoService::class.java, "all") - var info = service.getTroopMember(groupId.toString(), uin.toString()) - if (refresh || !service.isMemberInCache(groupId.toString(), uin.toString()) || info == null || info.troopnick == null) { - info = requestTroopMemberInfo(service, groupId, uin).getOrNull() - } - if (info == null) { - info = getTroopMemberInfoByUinViaNt(groupId, uin).getOrNull()?.let { - TroopMemberInfo().apply { - troopnick = it.cardName - friendnick = it.nick - } - } - } - try { - if (info != null && (info.alias == null || info.alias.isBlank())) { - val req = group_member_info.ReqBody() - req.uint64_group_code.set(groupId) - req.uint64_uin.set(uin.toLong()) - req.bool_new_client.set(true) - req.uint32_client_type.set(1) - req.uint32_rich_card_name_ver.set(1) - val respBuffer = sendBufferAW("group_member_card.get_group_member_card_info", true, req.toByteArray()) - if (respBuffer != null) { - val rsp = group_member_info.RspBody() - rsp.mergeFrom(respBuffer.slice(4)) - if (rsp.msg_meminfo.str_location.has()) { - info.alias = rsp.msg_meminfo.str_location.get().toStringUtf8() - } - if (rsp.msg_meminfo.uint32_age.has()) { - info.age = rsp.msg_meminfo.uint32_age.get().toByte() - } - if (rsp.msg_meminfo.bytes_group_honor.has()) { - val honorBytes = rsp.msg_meminfo.bytes_group_honor.get().toByteArray() - val honor = troop_honor.GroupUserCardHonor() - honor.mergeFrom(honorBytes) - info.level = honor.level.get() - // 10315: medal_id not real group level - } - } - } - } catch (err: Throwable) { - LogCenter.log(err.stackTraceToString(), Level.WARN) - } - return if (info != null) { - Result.success(info) - } else { - Result.failure(Exception("获取群成员信息失败")) - } - } - - suspend fun getTroopMemberInfoByUinV2( - groupId: Long, - uin: Long, - refresh: Boolean = false - ): Result { - val service = app.getRuntimeService(ITroopMemberInfoService::class.java, "all") - var info = service.getTroopMember(groupId.toString(), uin.toString()) - if (refresh || !service.isMemberInCache(groupId.toString(), uin.toString()) || info == null || info.troopnick == null) { - info = requestTroopMemberInfo(service, groupId, uin, timeout = 2000).getOrNull() - } - if (info == null) { - info = getTroopMemberInfoByUinViaNt(groupId, uin, timeout = 2000L).getOrNull()?.let { - TroopMemberInfo().apply { - troopnick = it.cardName - friendnick = it.nick - } - } - } - try { - if (info != null && (info.alias == null || info.alias.isBlank())) { - val req = group_member_info.ReqBody() - req.uint64_group_code.set(groupId) - req.uint64_uin.set(uin) - req.bool_new_client.set(true) - req.uint32_client_type.set(1) - req.uint32_rich_card_name_ver.set(1) - val respBuffer = sendBufferAW("group_member_card.get_group_member_card_info", true, req.toByteArray(), timeout = 2000) - if (respBuffer != null) { - val rsp = group_member_info.RspBody() - rsp.mergeFrom(respBuffer.slice(4)) - if (rsp.msg_meminfo.str_location.has()) { - info.alias = rsp.msg_meminfo.str_location.get().toStringUtf8() - } - if (rsp.msg_meminfo.uint32_age.has()) { - info.age = rsp.msg_meminfo.uint32_age.get().toByte() - } - if (rsp.msg_meminfo.bytes_group_honor.has()) { - val honorBytes = rsp.msg_meminfo.bytes_group_honor.get().toByteArray() - val honor = troop_honor.GroupUserCardHonor() - honor.mergeFrom(honorBytes) - info.level = honor.level.get() - // 10315: medal_id not real group level - } - } - } - } catch (err: Throwable) { - LogCenter.log(err.stackTraceToString(), Level.WARN) - } - return if (info != null) { - Result.success(info) - } else { - Result.failure(Exception("获取群成员信息失败")) - } - } - - suspend fun getTroopMemberInfoByUinViaNt( - groupId: Long, - qq: Long, - timeout: Long = 5000L - ): Result { - return runCatching { - val kernelService = NTServiceFetcher.kernelService - val sessionService = kernelService.wrapperSession - val groupService = sessionService.groupService - val info = withTimeoutOrNull(timeout) { - suspendCancellableCoroutine { - groupService.getTransferableMemberInfo(groupId) { code, _, data -> - if (code != 0) { - it.resume(null) - return@getTransferableMemberInfo - } - data.forEach { (_, info) -> - if (info.uin == qq) { - it.resume(info) - return@forEach - } - } - it.resume(null) - } - } - } - return if (info != null) { - Result.success(info) - } else { - Result.failure(Exception("获取群成员信息失败")) - } - } - } - - suspend fun getTroopMemberInfoByUid(groupId: Long, uid: String): Result { - val kernelService = NTServiceFetcher.kernelService - val sessionService = kernelService.wrapperSession - val groupService = sessionService.groupService - val info = suspendCancellableCoroutine { - groupService.getTransferableMemberInfo(groupId) { code, _, data -> - if (code != 0) { - it.resume(null) - return@getTransferableMemberInfo - } - data.forEach { (tmpUid, info) -> - if (tmpUid == uid) { - it.resume(info) - return@forEach - } - } - } - } - return if (info != null) { - Result.success(info) - } else { - Result.failure(Exception("获取群成员信息失败")) - } - } - - private suspend fun requestTroopMemberInfo(service: ITroopMemberInfoService, groupId: Long): Result> { - val info = RefreshTroopMemberListLock.withLock { - service.deleteTroopMembers(groupId.toString()) - refreshTroopMemberList(groupId) - - withTimeoutOrNull(10000) { - var memberList: List? - do { - delay(100) - memberList = service.getAllTroopMembers(groupId.toString()) - } while (memberList.isNullOrEmpty()) - return@withTimeoutOrNull memberList - } - } - return if (info != null) { - Result.success(info) - } else { - Result.failure(Exception("获取群成员信息失败")) - } - } - - private suspend fun requestGroupInfo( - service: ITroopInfoService - ): Boolean { - refreshTroopList() - - return suspendCancellableCoroutine { continuation -> - val waiter = GlobalScope.launch { - do { - delay(1000) - } while ( - !service.isTroopCacheInited - ) - continuation.resume(true) - } - continuation.invokeOnCancellation { - waiter.cancel() - continuation.resume(false) - } - } - } - - private fun refreshTroopMemberList(groupId: Long) { - val app = AppRuntimeFetcher.appRuntime - if (app !is AppInterface) - throw RuntimeException("AppRuntime cannot cast to AppInterface") - val businessHandler = app.getBusinessHandler(BusinessHandlerFactory.TROOP_MEMBER_LIST_HANDLER) - - // void C(boolean forceRefresh, String groupId, String troopcode, int reqType); // RequestedTroopList/refreshMemberListFromServer - if (!GroupSvc::METHOD_REQ_TROOP_MEM_LIST.isInitialized) { - METHOD_REQ_TROOP_MEM_LIST = businessHandler.javaClass.declaredMethods.first { - it.parameterCount == 4 - && it.parameterTypes[0] == Boolean::class.java - && it.parameterTypes[1] == String::class.java - && it.parameterTypes[2] == String::class.java - && it.parameterTypes[3] == Int::class.java - && !Modifier.isPrivate(it.modifiers) - } - } - - METHOD_REQ_TROOP_MEM_LIST.invoke(businessHandler, true, groupId.toString(), groupUin2GroupCode(groupId).toString(), 5) - } - - private fun refreshTroopList() { - val app = AppRuntimeFetcher.appRuntime - if (app !is AppInterface) - throw RuntimeException("AppRuntime cannot cast to AppInterface") - val businessHandler = app.getBusinessHandler(BusinessHandlerFactory.TROOP_LIST_HANDLER) - - if (!GroupSvc::METHOD_REQ_TROOP_LIST.isInitialized) { - METHOD_REQ_TROOP_LIST = businessHandler.javaClass.declaredMethods.first { - it.parameterCount == 0 && !Modifier.isPrivate(it.modifiers) && it.returnType == Void.TYPE - } - } - - METHOD_REQ_TROOP_LIST.invoke(businessHandler) - } - - private fun requestMemberInfo(groupId: Long, memberUin: Long) { - val app = AppRuntimeFetcher.appRuntime - if (app !is AppInterface) - throw RuntimeException("AppRuntime cannot cast to AppInterface") - val businessHandler = app.getBusinessHandler(BusinessHandlerFactory.TROOP_MEMBER_CARD_HANDLER) - - if (!GroupSvc::METHOD_REQ_MEMBER_INFO.isInitialized) { - METHOD_REQ_MEMBER_INFO = businessHandler.javaClass.declaredMethods.first { - it.parameterCount == 2 && - it.parameterTypes[0] == Long::class.java && - it.parameterTypes[1] == Long::class.java && - !Modifier.isPrivate(it.modifiers) - } - } - - METHOD_REQ_MEMBER_INFO.invoke(businessHandler, groupId, memberUin) - } - - private fun requestMemberInfoV2(groupId: Long, memberUin: Long) { - val app = AppRuntimeFetcher.appRuntime - if (app !is AppInterface) - throw RuntimeException("AppRuntime cannot cast to AppInterface") - val businessHandler = app.getBusinessHandler(BusinessHandlerFactory.TROOP_MEMBER_CARD_HANDLER) - - if (!GroupSvc::METHOD_REQ_MEMBER_INFO_V2.isInitialized) { - METHOD_REQ_MEMBER_INFO_V2 = businessHandler.javaClass.declaredMethods.first { - it.parameterCount == 3 && - it.parameterTypes[0] == String::class.java && - it.parameterTypes[1] == String::class.java && - !Modifier.isPrivate(it.modifiers) - } - } - - METHOD_REQ_MEMBER_INFO_V2.invoke(businessHandler, groupId.toString(), groupUin2GroupCode(groupId).toString(), arrayListOf(memberUin.toString())) - } - - private suspend fun requestGroupInfo(dataService: ITroopInfoService, uin: Long): Result { - val strUin = uin.toString() - val info = withTimeoutOrNull(1000) { - var troopInfo: TroopInfo? - do { - troopInfo = dataService.getTroopInfo(strUin) - delay(100) - } while (troopInfo == null || troopInfo.troopuin.isNullOrBlank()) - return@withTimeoutOrNull troopInfo - } - return if (info != null) { - Result.success(info) - } else { - Result.failure(Exception("获取群列表失败")) - } - } - - private suspend fun requestTroopMemberInfo(service: ITroopMemberInfoService, groupId: Long, memberUin: Long, timeout: Long = 10_000): Result { - val info = RefreshTroopMemberInfoLock.withLock { - val groupIdStr = groupId.toString() - val memberUinStr = memberUin.toString() - - service.deleteTroopMember(groupIdStr, memberUinStr) - - requestMemberInfoV2(groupId, memberUin) - requestMemberInfo(groupId, memberUin) - - withTimeoutOrNull(timeout) { - while (!service.isMemberInCache(groupIdStr, memberUinStr)) { - delay(200) - } - return@withTimeoutOrNull service.getTroopMember(groupIdStr, memberUinStr) - } - } - return if (info != null) { - Result.success(info) - } else { - Result.failure(Exception("获取群成员信息失败")) - } - } - - // ProfileService.Pb.ReqSystemMsgAction.Group - suspend fun requestGroupRequest( - msgSeq: Long, - uin: Long, - gid: Long, - msg: String? = "", - approve: Boolean? = true, - notSee: Boolean? = false, - subType: String - ): Result{ - 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 respBuffer = sendBufferAW("ProfileService.Pb.ReqSystemMsgAction.Group", true, req.toByteArray()) - ?: return Result.failure(Exception("操作失败")) - val rsp = structmsg.RspSystemMsgAction().mergeFrom(respBuffer.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 { - 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 respBuffer = sendBufferAW("ProfileService.Pb.ReqSystemMsgNew.Group", true, req.toByteArray()) - return if (respBuffer == null) { - ArrayList() - } else { - try { - val msg = structmsg.RspSystemMsgNew() - msg.mergeFrom(respBuffer.slice(4)) - return msg.groupmsgs.get().orEmpty() - } catch (err: Throwable) { - requestGroupSystemMsgNew(msgNum, reqMsgType, latestFriendSeq, latestGroupSeq, retryCnt - 1) - } - } - } - - - @OptIn(ExperimentalSerializationApi::class) - suspend fun getEssenceMessageList(groupId: Long, page: Int = 0, pageSize: Int = 20): Result>{ -// GlobalClient.get() - val cookie = TicketSvc.getCookie("qun.qq.com") - val bkn = TicketSvc.getBkn(TicketSvc.getRealSkey(TicketSvc.getUin())) - val url = "https://qun.qq.com/cgi-bin/group_digest/digest_list?bkn=${bkn}&group_code=${groupId}&page_start=${page}&page_limit=${pageSize}" - val response = GlobalClient.get(url) { - header("Cookie", cookie) - } - val body = Json.decodeFromStream(response.body()) - if (body.jsonObject["retcode"].asInt == 0) { - val data = body.jsonObject["data"].asJsonObject - val list = data["msg_list"].asJsonArrayOrNull - ?: // is_end - return Result.success(ArrayList()) - return Result.success(list.map { - val obj = it.jsonObject - val msgSeq = obj["msg_seq"].asInt - val msg = EssenceMessage( - senderId = obj["sender_uin"].asString.toLong(), - senderNick = obj["sender_nick"].asString, - senderTime = obj["sender_time"].asLong, - operatorId = obj["add_digest_uin"].asString.toLong(), - operatorNick = obj["add_digest_nick"].asString, - operatorTime = obj["add_digest_time"].asLong, - messageId = 0, - messageSeq = msgSeq, - realId = msgSeq, - messageContent = obj["msg_content"] ?: EmptyJsonArray - ) - val mapping = MessageHelper.getMsgMappingBySeq(MsgConstant.KCHATTYPEGROUP, groupId.toString(), msgSeq) - if (mapping != null) { - msg.messageId = mapping.msgHashId - } - msg - }) - } else { - return Result.failure(Exception(body.jsonObject["retmsg"].asStringOrNull)) - } - } - - @OptIn(ExperimentalSerializationApi::class) - suspend fun getGroupAnnouncements(groupId: Long): Result>{ - val cookie = TicketSvc.getCookie("qun.qq.com") - val bkn = TicketSvc.getBkn(TicketSvc.getRealSkey(TicketSvc.getUin())) - val url = "https://web.qun.qq.com/cgi-bin/announce/get_t_list?bkn=${bkn}&qid=${groupId}&ft=23&s=-1&n=20" - val response = GlobalClient.get(url) { - header("Cookie", cookie) - } - val body = Json.decodeFromStream(response.body()) - if (body.jsonObject["ec"].asInt == 0) { - val list = body.jsonObject["feeds"].asJsonArrayOrNull - ?: return Result.success(ArrayList()) - return Result.success(list.map { - val obj = it.jsonObject - GroupAnnouncement( - senderId = obj["u"].asLong, - publishTime = obj["pubt"].asLong, - message = GroupAnnouncementMessage( -// text = obj["msg"].asJsonObject["text"].asString, - text = fromHtml(obj["msg"].asJsonObject["text"].asString), - images = obj["msg"].asJsonObject["pics"].asJsonArrayOrNull?.map { pic -> - GroupAnnouncementMessageImage( - id = pic.jsonObject["id"].asString, - width = pic.jsonObject["w"].asString, - height = pic.jsonObject["h"].asString, - ) - } ?: ArrayList() - ) - ) - }) - } else { - return Result.failure(Exception(body.jsonObject["em"].asStringOrNull)) - } - } - - private fun fromHtml(htmlString: String): String { - return HtmlCompat - // 特殊处理 ,目的是替换为换行符,否则会被fromHtml忽略并移除 - .fromHtml(htmlString.replace(" ", "[shamrockplaceholder]"), HtmlCompat.FROM_HTML_MODE_LEGACY) - .toString() - .replace("[shamrockplaceholder]", "\n") - } - - @OptIn(ExperimentalSerializationApi::class) - suspend fun uploadImageTroopNotice(image: String): Result { - val file = FileUtils.parseAndSave(image) - val cookie = TicketSvc.getCookie("qun.qq.com") - val bkn = TicketSvc.getBkn(TicketSvc.getRealSkey(TicketSvc.getUin())) - val response = GlobalClient.post("https://web.qun.qq.com/cgi-bin/announce/upload_img") { - headers { - header("Cookie", cookie) - } - contentType(ContentType.MultiPart.FormData) - setBody(MultiPartFormDataContent( - // 黑人问号 ktor默认formdata传的tx不认。默认是name=bkn,非要写成name="bkn"才认? - formData { - append("filename", "001.png", Headers.build { - append(HttpHeaders.ContentDisposition, "name=\"filename\"") - }) - append("source", "troopNotice", Headers.build { - append(HttpHeaders.ContentDisposition, "name=\"source\"") - }) - append("bkn", bkn, Headers.build { - append(HttpHeaders.ContentDisposition, "name=\"bkn\"") - }) - append("m", "0", Headers.build { - append(HttpHeaders.ContentDisposition, "name=\"m\"") - }) - append("pic_up", file.readBytes(), Headers.build { - append(HttpHeaders.ContentType, "image/png") - append(HttpHeaders.ContentDisposition, "name=\"pic_up\" filename=\"001.png\"") - - }) - } - )) - } - val body = Json.decodeFromStream(response.body()) - if (body.jsonObject["ec"].asInt == 0) { - var idJsonStr = body.jsonObject["id"].asStringOrNull - return if (idJsonStr != null) { - idJsonStr = idJsonStr.replace(""", "\"") - val idJson = Json.decodeFromString(idJsonStr) - LogCenter.log(idJson.toString()) - Result.success(GroupAnnouncementMessageImage( - height = idJson.asJsonObject["h"].asString, - width = idJson.asJsonObject["w"].asString, - id = idJson.asJsonObject["id"].asString, - )) - } else { - Result.failure(Exception("图片上传失败")) - } - } else { - return Result.failure(Exception(body.jsonObject["em"].asStringOrNull)) - } - } - - @OptIn(ExperimentalSerializationApi::class) - suspend fun addQunNotice(groupId: Long, text: String, image: GroupAnnouncementMessageImage?) : Result { - val cookie = TicketSvc.getCookie("qun.qq.com") - val bkn = TicketSvc.getBkn(TicketSvc.getRealSkey(TicketSvc.getUin())) - val response = GlobalClient.submitForm( - url = "https://web.qun.qq.com/cgi-bin/announce/add_qun_notice", - formParameters = parameters { - append("qid", groupId.toString()) - append("bkn", bkn) - append("text", text) - append("pinned", "0") - append("type", "1") - // todo allow custom settings - append("settings", "{\"is_show_edit_card:\"1,\"tip_window_type\":1,\"confirm_required\":1}") - if (null != image) { - append("pic", image.id) - append("imgWidth", image.width) - append("imgHeight", image.height) - } - }, - block = { - headers { - header("Cookie", cookie) - } - } - ) - val body = Json.decodeFromStream(response.body()) - return if (body.jsonObject["ec"].asInt == 0) { - Result.success(true) - } else { - Result.failure(Exception(body.jsonObject["em"].asStringOrNull)) - } - } - - suspend fun groupSign(groupId: Long): Result { - val req = oidb_0xeb7.ReqBody() - val signInWriteReq = oidb_0xeb7.StSignInWriteReq() - signInWriteReq.groupId.set(groupId.toString()) - signInWriteReq.uid.set(getUin()) - var version = PlatformUtils.getClientVersion(MobileQQ.getContext()) - version = version.replace("android", "").trimStart() - signInWriteReq.clientVersion.set(version) - req.signInWriteReq.set(signInWriteReq) - val buffer = sendOidbAW("OidbSvc.0xeb7", 3767, 1, req.toByteArray()) - return if (buffer == null) { - Result.failure(Exception("操作失败")) - } else { - val body = oidb_sso.OIDBSSOPkg() - body.mergeFrom(buffer.slice(4)) - val rsp = oidb_0xeb7.RspBody() - rsp.mergeFrom(body.bytes_bodybuffer.get().toByteArray()) - val doneInfo = rsp.signInWriteRsp.doneInfo - LogCenter.log(rsp.toString(), Level.DEBUG) - Result.success("${doneInfo.leftTitleWrod.get()} ${doneInfo.rightDescWord.get()} ${doneInfo.belowPortraitWords.get().joinToString(" ")}") - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/LbsSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/LbsSvc.kt deleted file mode 100644 index 84c50149..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/LbsSvc.kt +++ /dev/null @@ -1,57 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet - -import com.tencent.biz.map.trpcprotocol.LbsSendInfo -import com.tencent.mobileqq.msf.core.MsfCore -import com.tencent.proto.lbsshare.LBSShare -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import moe.fuqiuluo.shamrock.helper.IllegalParamsException -import moe.fuqiuluo.shamrock.tools.slice -import kotlin.math.roundToInt - -internal object LbsSvc: BaseSvc() { - suspend fun tryShareLocation(chatType: Int, peerId: Long, lat: Double, lon: Double): Result { - val req = LbsSendInfo.SendMessageReq() - req.uint64_peer_account.set(peerId) - when (chatType) { - MsgConstant.KCHATTYPEGROUP -> req.enum_relation_type.set(1) - MsgConstant.KCHATTYPEC2C -> req.enum_relation_type.set(0) - else -> error("Not supported chat type: $chatType") - } - req.str_name.set("位置分享") - req.str_address.set(getAddressWithLonLat(lat, lon).onFailure { - return Result.failure(it) - }.getOrNull()) - req.str_lat.set(lat.toString()) - req.str_lng.set(lon.toString()) - sendPb("trpc.qq_lbs.qq_lbs_ark.LocationArk.SsoSendMessage", req.toByteArray(), MsfCore.getNextSeq()) - - return Result.success(Unit) - } - - suspend fun getAddressWithLonLat(lat: Double, lon: Double): Result { - if (lat > 90 || lat < 0) { - return Result.failure(IllegalParamsException("纬度大小错误")) - } - if (lon > 180 || lon < 0) { - return Result.failure(IllegalParamsException("经度大小错误")) - } - val latO = (lat * 1000000).roundToInt() - val lngO = (lon * 1000000).roundToInt() - val req = LBSShare.LocationReq() - req.lat.set(latO) - req.lng.set(lngO) - req.coordinate.set(1) - req.keyword.set("") - req.category.set("") - req.page.set(0) - req.count.set(20) - req.requireMyLbs.set(1) - req.imei.set("") - val buffer = sendBufferAW("LbsShareSvr.location", true, req.toByteArray()) - ?: return Result.failure(Exception("获取位置失败")) - val resp = LBSShare.LocationResp() - resp.mergeFrom(buffer.slice(4)) - val location = resp.mylbs - return Result.success(location.addr.get()) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/MsgSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/MsgSvc.kt deleted file mode 100644 index fda842b6..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/MsgSvc.kt +++ /dev/null @@ -1,524 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet - -import com.tencent.mobileqq.qroute.QRoute -import com.tencent.mobileqq.troop.api.ITroopMemberNameService -import com.tencent.qqnt.kernel.api.IKernelService -import com.tencent.qqnt.kernel.nativeinterface.* -import com.tencent.qqnt.msg.api.IMsgService -import kotlinx.coroutines.delay -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.withTimeoutOrNull -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonObject -import moe.fuqiuluo.qqinterface.servlet.msg.MessageSegment -import moe.fuqiuluo.qqinterface.servlet.msg.toJson -import moe.fuqiuluo.qqinterface.servlet.msg.toListMap -import moe.fuqiuluo.qqinterface.servlet.msg.toSegments -import moe.fuqiuluo.shamrock.helper.ContactHelper -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.remote.service.data.MessageDetail -import moe.fuqiuluo.shamrock.remote.service.data.MessageSender -import moe.fuqiuluo.shamrock.remote.structures.SendMsgResult -import moe.fuqiuluo.shamrock.tools.* -import moe.fuqiuluo.shamrock.utils.DeflateTools -import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher -import moe.fuqiuluo.shamrock.xposed.helper.msgService -import moe.fuqiuluo.symbols.decodeProtobuf -import protobuf.auto.toByteArray -import protobuf.message.* -import protobuf.message.longmsg.* -import java.util.* -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine -import kotlin.random.Random - -internal object MsgSvc : BaseSvc() { - private suspend fun prepareTempChatFromGroup( - groupId: String, - peerId: String - ): Result { - LogCenter.log("主动临时消息,创建临时会话。", Level.INFO) - val msgService = app.getRuntimeService(IKernelService::class.java, "all").msgService - ?: return Result.failure(Exception("获取消息服务失败")) - msgService.prepareTempChat( - TempChatPrepareInfo( - MsgConstant.KCHATTYPETEMPC2CFROMGROUP, - ContactHelper.getUidByUinAsync(peerId = peerId.toLong()), - app.getRuntimeService(ITroopMemberNameService::class.java, "all") - .getTroopMemberNameRemarkFirst(groupId, peerId), - groupId, - EMPTY_BYTE_ARRAY, - app.currentUid, - "", - TempChatGameSession() - ) - ) { code, reason -> - if (code != 0) { - LogCenter.log("临时会话创建失败: $code, $reason", Level.ERROR) - } - } - return Result.success(Unit) - } - - suspend fun getTempChatInfo(chatType: Int, uid: String): Result { - val msgService = app.getRuntimeService(IKernelService::class.java, "all").msgService - ?: return Result.failure(Exception("获取消息服务失败")) - val info: TempChatInfo = withTimeoutOrNull(5000) { - suspendCancellableCoroutine { - msgService.getTempChatInfo(chatType, uid) { code, msg, tempChatInfo -> - if (code == 0) { - it.resume(tempChatInfo) - } else { - LogCenter.log("获取临时会话信息失败: $code:$msg", Level.ERROR) - it.resume(null) - } - } - } - } ?: return Result.failure(Exception("获取临时会话信息失败")) - return Result.success(info) - } - - /** - * 正常获取 - */ - suspend fun getMsg(hash: Int): Result { - val mapping = MessageHelper.getMsgMappingByHash(hash) - ?: return Result.failure(Exception("没有对应消息映射,消息获取失败")) - - val peerId = mapping.peerId - val contact = MessageHelper.generateContact(mapping.chatType, peerId, mapping.subPeerId) - - val msg = withTimeoutOrNull(5000) { - val service = QRoute.api(IMsgService::class.java) - suspendCancellableCoroutine { continuation -> - service.getMsgsByMsgId(contact, arrayListOf(mapping.qqMsgId)) { code, _, msgRecords -> - if (code == 0 && msgRecords.isNotEmpty()) { - continuation.resume(msgRecords.first()) - } else { - continuation.resume(null) - } - } - continuation.invokeOnCancellation { - continuation.resume(null) - } - } - } - - return if (msg != null) { - Result.success(msg) - } else { - Result.failure(Exception("获取消息失败")) - } - } - - suspend fun getMsgByQMsgId( - chatType: Int, - peerId: String, - qqMsgId: Long, - subPeerId: String = "" - ): Result { - val contact = MessageHelper.generateContact(chatType, peerId, subPeerId) - val service = QRoute.api(IMsgService::class.java) - - val msg = withTimeoutOrNull(5000) { - suspendCoroutine { continuation -> - service.getMsgsByMsgId(contact, arrayListOf(qqMsgId)) { code, _, msgRecords -> - if (code == 0 && msgRecords.isNotEmpty()) { - continuation.resume(msgRecords.first()) - } else { - continuation.resume(null) - } - } - } - } - - return if (msg != null) { - Result.success(msg) - } else { - Result.failure(Exception("获取消息失败")) - } - } - - /** - * 什么鸟屎都获取不到 - */ - suspend fun getMsgBySeq( - chatType: Int, - peerId: String, - seq: Long - ): Result { - val contact = MessageHelper.generateContact(chatType, peerId) - val msg = withTimeoutOrNull(1000) { - val service = QRoute.api(IMsgService::class.java) - suspendCancellableCoroutine { continuation -> - service.getMsgsBySeqs(contact, arrayListOf(seq)) { code, _, msgRecords -> - continuation.resume(msgRecords?.firstOrNull()) - } - continuation.invokeOnCancellation { - continuation.resume(null) - } - } - } - return if (msg != null) { - Result.success(msg) - } else { - Result.failure(Exception("获取消息失败")) - } - } - - /** - * 撤回消息 同步 HTTP API - */ - suspend fun recallMsg(msgHash: Int): Pair { - val kernelService = NTServiceFetcher.kernelService - val sessionService = kernelService.wrapperSession - val msgService = sessionService.msgService - - val mapping = MessageHelper.getMsgMappingByHash(msgHash) - ?: return -1 to "无法找到消息映射" - - val contact = MessageHelper.generateContact(mapping.chatType, mapping.peerId, mapping.subPeerId) - - return suspendCancellableCoroutine { continuation -> - msgService.recallMsg(contact, arrayListOf(mapping.qqMsgId)) { code, why -> - continuation.resume(code to why) - } - } - } - - /** - * 发送消息 - * - * Aio 腾讯内部命名 All In One - */ - suspend fun sendToAio( - chatType: Int, - peedId: String, - message: JsonArray, - fromId: String = peedId, - retryCnt: Int - ): Result { - // 主动临时消息 - when (chatType) { - MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> { - prepareTempChatFromGroup(fromId, peedId).onFailure { - LogCenter.log("主动临时消息,创建临时会话失败。", Level.ERROR) - return Result.failure(Exception("主动临时消息,创建临时会话失败。")) - } - } - } - val result = - MessageHelper.sendMessageWithoutMsgId(chatType, peedId, message, fromId, MessageCallback(peedId, 0)) - .getOrElse { return Result.failure(it) } - return if (result.isTimeout) { - // 发送失败,可能网络问题出现红色感叹号,重试 - // 例如 rich media transfer failed - delay(100) - MessageHelper.resendMsg(chatType, peedId, fromId, result.qqMsgId, retryCnt, result.msgHashId) - } else { - Result.success(result) - } - } - - suspend fun uploadMultiMsg( - chatType: Int, - peerId: String, - fromId: String = peerId, - messages: JsonArray, - retryCnt: Int - ): Result { - return uploadMultiMsg(chatType, peerId, fromId, messages).onFailure { - if (retryCnt > 0) { - return uploadMultiMsg(chatType, peerId, fromId, messages, retryCnt - 1) - } - } - } - - private suspend fun uploadMultiMsg( - chatType: Int, - peerId: String, - fromId: String = peerId, - messages: JsonArray, - ): Result { - var i = -1 - val desc = MutableList(messages.size) { "" } - val forwardMsg = mutableMapOf() - - val msgs = messages.mapNotNull { msg -> - kotlin.runCatching { - val data = msg.asJsonObject["data"].asJsonObject - if (data.containsKey("id")) { - val msgId = data["id"].asInt - val record = getMsg(msgId).onFailure { - error("合并转发消息节点消息(id = ${data["id"].asInt})获取失败:$it") - }.getOrThrow() - PushMsgBody( - msgHead = ResponseHead( - peerUid = record.senderUid, - receiverUid = record.peerUid, - forward = ResponseForward( - friendName = record.sendNickName - ), - responseGrp = if (record.chatType == MsgConstant.KCHATTYPEGROUP) ResponseGrp( - groupCode = record.peerUin.toULong(), - memberCard = record.sendMemberName, - u1 = 2 - ) else null - ), - contentHead = ContentHead( - msgType = when (record.chatType) { - MsgConstant.KCHATTYPEC2C -> 9 - MsgConstant.KCHATTYPEGROUP -> 82 - else -> throw UnsupportedOperationException("Unsupported chatType: $chatType") - }, - msgSubType = if (record.chatType == MsgConstant.KCHATTYPEC2C) 175 else null, - divSeq = if (record.chatType == MsgConstant.KCHATTYPEC2C) 175 else null, - msgViaRandom = record.msgId, - sequence = record.msgSeq, // idk what this is(i++) - msgTime = record.msgTime, - u2 = 1, - u6 = 0, - u7 = 0, - msgSeq = if (record.chatType == MsgConstant.KCHATTYPEC2C) record.msgSeq else null, // seq for dm - forwardHead = ForwardHead( - u1 = 0, - u2 = 0, - u3 = 0, - ub641 = "", - avatar = "" - ) - ), - body = MsgBody( - richText = MessageHelper.messageArrayToRichText( - record.chatType, - record.msgId, - record.peerUin.toString(), - record.elements.toSegments( - record.chatType, - record.peerUin.toString(), - "0" - ).onEach { segment -> - if (segment.type == "forward") { - forwardMsg[segment.data["filename"] as String] = - segment.data["id"] as String - } - }.toJson() - ).onFailure { - error("消息合成失败: ${it.stackTraceToString()}") - }.onSuccess { - desc[++i] = record.sendMemberName.ifEmpty { record.sendNickName } + ": " + it.first - }.getOrThrow().second - ) - ) - } else if (data.containsKey("content")) { - PushMsgBody( - msgHead = ResponseHead( - peer = data["uin"]?.asLong ?: TicketSvc.getUin().toLong(), - peerUid = data["uid"]?.asString ?: TicketSvc.getUid(), - receiverUid = TicketSvc.getUid(), - forward = ResponseForward( - friendName = data["name"]?.asStringOrNull ?: TicketSvc.getNickname() - ) - ), - contentHead = ContentHead( - msgType = 9, - msgSubType = 175, - divSeq = 175, - msgViaRandom = Random.nextLong(), - sequence = data["seq"]?.asLong ?: Random.nextLong(), - msgTime = data["time"]?.asLong ?: (System.currentTimeMillis() / 1000), - u2 = 1, - u6 = 0, - u7 = 0, - msgSeq = data["seq"]?.asLong ?: Random.nextLong(), - forwardHead = ForwardHead( - u1 = 0, - u2 = 0, - u3 = 2, - ub641 = "", - avatar = "" - ) - ), - body = MsgBody( - richText = MessageHelper.messageArrayToRichText( - chatType = chatType, - msgId = Random.nextLong(), - peerId = data["uin"]?.asString ?: TicketSvc.getUin(), - messageList = when (data["content"]) { - is JsonObject -> listOf(data["content"] as JsonObject).json - is JsonArray -> data["content"] as JsonArray - else -> MessageHelper.decodeCQCode(data["content"].asString) - }.onEach { element -> - val elementData = element.asJsonObject["data"].asJsonObject - if (element.asJsonObject["type"].asString == "forward") { - forwardMsg[elementData["filename"].asString] = - elementData["id"].asString - } - } - ).onSuccess { - desc[++i] = (data["name"].asStringOrNull ?: data["uin"].asStringOrNull - ?: TicketSvc.getNickname()) + ": " + it.first - }.onFailure { - error("消息合成失败: ${it.stackTraceToString()}") - }.getOrThrow().second - ) - ) - } else error("消息节点缺少id或content字段") - }.onFailure { - LogCenter.log("消息节点解析失败:${it.stackTraceToString()}", Level.WARN) - }.getOrNull() - }.ifEmpty { - return Result.failure(Exception("消息节点为空")) - } - - val payload = LongMsgPayload( - action = mutableListOf( - LongMsgAction( - command = "MultiMsg", - data = LongMsgContent( - body = msgs - ) - ) - ).apply { - forwardMsg.map { msg -> - addAll(getMultiMsg(msg.value).getOrElse { return Result.failure(Exception("无法获取嵌套转发消息: $it")) } - .map { action -> - if (action.command == "MultiMsg") LongMsgAction( - command = msg.key, - data = action.data - ) else action - }) - } - } - ) - LogCenter.log({ payload.toByteArray().toHexString() }, Level.DEBUG) - - val req = LongMsgReq( - sendInfo = when (chatType) { - MsgConstant.KCHATTYPEC2C -> SendLongMsgInfo( - type = 1, - uid = LongMsgUid(if(peerId.startsWith("u_")) peerId else ContactHelper.getUidByUinAsync(peerId.toLong()) ), - payload = DeflateTools.gzip(payload.toByteArray()) - ) - MsgConstant.KCHATTYPEGROUP -> SendLongMsgInfo( - type = 3, - uid = LongMsgUid(fromId), - groupUin = fromId.toULong(), - payload = DeflateTools.gzip(payload.toByteArray()) - ) - else -> throw UnsupportedOperationException("Unsupported chatType: $chatType") - }, - setting = LongMsgSettings( - field1 = 4, - field2 = 2, - field3 = 9, - field4 = 0 - ) - ).toByteArray() - - val buffer = sendBufferAW("trpc.group.long_msg_interface.MsgService.SsoSendLongMsg", true, req, timeout = 30_000) - ?: return Result.failure(Exception("unable to upload multi message, response timeout")) - val rsp = runCatching { - buffer.slice(4).decodeProtobuf() - }.getOrElse { - buffer.decodeProtobuf() - } - val resId = rsp.sendResult?.resId ?: return Result.failure(Exception("unable to upload multi message")) - return Result.success(MessageSegment( - type = "forward", - data = mapOf( - "id" to resId, - "filename" to UUID.randomUUID().toString(), - "summary" to "查看${desc.size}条转发消息", - "desc" to desc.slice(0..if (i < 3) i else 3).joinToString("\n") - ) - )) - } - - suspend fun getMultiMsg(resId: String): Result> { - val req = LongMsgReq( - recvInfo = RecvLongMsgInfo( - uid = LongMsgUid(TicketSvc.getUid()), - resId = resId, - u1 = 3 - ), - setting = LongMsgSettings( - field1 = 2, - field2 = 2, - field3 = 9, - field4 = 0 - ) - ) - val buffer = sendBufferAW( - "trpc.group.long_msg_interface.MsgService.SsoRecvLongMsg", - true, - req.toByteArray() - ) ?: return Result.failure(Exception("unable to get multi message")) - val rsp = buffer.slice(4).decodeProtobuf() - val zippedPayload = DeflateTools.ungzip( - rsp.recvResult?.payload ?: return Result.failure(Exception("payload is empty")) - ) - LogCenter.log(zippedPayload.toHexString(), Level.DEBUG) - return Result.success( - zippedPayload.decodeProtobuf().action - ?: return Result.failure(Exception("action is empty")) - ) - } - - suspend fun getForwardMsg(resId: String): Result> { - val result = getMultiMsg(resId).getOrElse { return Result.failure(it) } - result.forEach { - if (it.command == "MultiMsg") { - return Result.success(it.data?.body?.map { msg -> - val chatType = - if (msg.contentHead!!.msgType == 82) MsgConstant.KCHATTYPEGROUP else MsgConstant.KCHATTYPEC2C - MessageDetail( - time = msg.contentHead?.msgTime?.toInt() ?: 0, - msgType = MessageHelper.obtainDetailTypeByMsgType(chatType), - msgId = 0, // msgViaRandom为空 tx不给 - qqMsgId = 0, - msgSeq = msg.contentHead!!.msgSeq ?: 0, - realId = msg.contentHead!!.msgSeq ?: 0, - sender = MessageSender( - msg.msgHead?.peer ?: 0, - msg.msgHead?.responseGrp?.memberCard ?: msg.msgHead?.forward?.friendName ?: "", - "unknown", - 0, - msg.msgHead?.peerUid ?: "", - msg.msgHead?.peerUid ?: "" - ), - message = msg.body?.richText?.toSegments( - chatType, - msg.msgHead?.peer.toString(), - "0" - )?.toListMap() ?: emptyList(), - peerId = msg.msgHead?.peer ?: 0, - groupId = if (chatType == MsgConstant.KCHATTYPEGROUP) msg.msgHead?.responseGrp?.groupCode?.toLong() - ?: 0 else 0, - targetId = if (chatType != MsgConstant.KCHATTYPEGROUP) msg.msgHead?.peer ?: 0 else 0 - ) - } ?: return Result.failure(Exception("Msg is empty"))) - } - } - return Result.failure(Exception("Can't find msg")) - } - - class MessageCallback( - private val peerId: String, - var msgHash: Int - ) : IOperateCallback { - override fun onResult(code: Int, reason: String?) { - if (code != 0 && msgHash != 0) { - MessageHelper.removeMsgByHashCode(msgHash) - } - when (code) { - 110 -> LogCenter.log("消息发送: $peerId, 无该联系人无法发送。") - 120 -> LogCenter.log("消息发送: $peerId, 禁言状态无法发送。") - 5 -> LogCenter.log("消息发送: $peerId, 当前不支持该消息类型。") - else -> LogCenter.log("消息发送: $peerId, code: $code $reason") - } - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/PacketSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/PacketSvc.kt deleted file mode 100644 index d74bf4e3..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/PacketSvc.kt +++ /dev/null @@ -1,112 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet - -import com.tencent.qqnt.kernel.nativeinterface.Contact -import com.tencent.qqnt.kernel.nativeinterface.IKernelMsgService -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import io.ktor.utils.io.core.BytePacketBuilder -import io.ktor.utils.io.core.readBytes -import io.ktor.utils.io.core.writeFully -import io.ktor.utils.io.core.writeInt -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.withTimeoutOrNull -import moe.fuqiuluo.qqinterface.servlet.msg.MessageTempHandler -import moe.fuqiuluo.shamrock.remote.action.handlers.GetHistoryMsg -import moe.fuqiuluo.shamrock.tools.broadcast -import moe.fuqiuluo.shamrock.utils.DeflateTools -import mqq.app.MobileQQ -import protobuf.auto.toByteArray -import protobuf.message.* -import protobuf.message.element.LightAppElem -import protobuf.push.MessagePush -import kotlin.coroutines.resume -import kotlin.text.toByteArray - -internal object PacketSvc : BaseSvc() { - /** - * 伪造收到Json卡片消息 - */ - suspend fun fakeSelfRecvJsonMsg(msgService: IKernelMsgService, content: String): Long { - return fakeReceiveSelfMsg(msgService) { - listOf( - Elem( - lightApp = LightAppElem((byteArrayOf(1) + DeflateTools.compress(content.toByteArray()))) - ) - ) - } - } - - private suspend fun fakeReceiveSelfMsg(msgService: IKernelMsgService, builder: () -> List): Long { - val latestMsg = withTimeoutOrNull(3000) { - suspendCancellableCoroutine { - msgService.getMsgs( - Contact(MsgConstant.KCHATTYPEC2C, app.currentUid, ""), - 0L, - 1, - true - ) { code, why, msgs -> - it.resume(GetHistoryMsg.GetMsgResult(code, why, msgs)) - } - } - }?.data?.firstOrNull() - val msgSeq = (latestMsg?.msgSeq ?: 0) + 1 - - val msgPush = MessagePush( - msgBody = PushMsgBody( - msgHead = ResponseHead( - peer = app.longAccountUin, - peerUid = app.currentUid, - flag = 1001, - receiver = app.longAccountUin, - receiverUid = app.currentUid - ), - contentHead = ContentHead( - msgType = 166, - msgSubType = 11, - msgSeq = msgSeq, - msgViaRandom = msgSeq, - msgTime = System.currentTimeMillis() / 1000, - u2 = 1, - sequence = msgSeq, - msgRandom = msgService.getMsgUniqueId(System.currentTimeMillis()), - u4 = msgSeq - 2, - u5 = msgSeq - ), - body = MsgBody( - RichText( - elements = builder() - ) - ) - ) - ) - - fakeReceive("trpc.msg.olpush.OlPushService.MsgPush", 10000, msgPush.toByteArray()) - return withTimeoutOrNull(5000L) { - suspendCancellableCoroutine { - MessageTempHandler.registerTemporaryMsgListener(msgSeq) { - it.resume(this.msgId) - } - it.invokeOnCancellation { - MessageTempHandler.unregisterTemporaryMsgListener(msgSeq) - } - } - } ?: -1L - } - - /** - * 伪造QQ收到某个包 - */ - private fun fakeReceive(cmd: String, seq: Int, buffer: ByteArray) { - MobileQQ.getContext().broadcast("msf") { - putExtra("__cmd", "fake_packet") - putExtra("package_cmd", cmd) - putExtra("package_uin", app.currentUin) - putExtra("package_seq", seq) - val wupBuffer = BytePacketBuilder().apply { - writeInt(buffer.size + 4) - writeFully(buffer) - }.build() - putExtra("package_buffer", wupBuffer.readBytes()) - wupBuffer.release() - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/QFavSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/QFavSvc.kt deleted file mode 100644 index 9b8bd0f7..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/QFavSvc.kt +++ /dev/null @@ -1,402 +0,0 @@ -@file:OptIn(ExperimentalSerializationApi::class) - -package moe.fuqiuluo.qqinterface.servlet - -import com.tencent.mobileqq.app.QQAppInterface -import com.tencent.mobileqq.transfile.HttpNetReq -import com.tencent.mobileqq.transfile.INetEngineListener -import com.tencent.mobileqq.transfile.NetReq -import com.tencent.mobileqq.transfile.NetResp -import com.tencent.mobileqq.transfile.ServerAddr -import com.tencent.mobileqq.transfile.api.IHttpEngineService -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.io.core.BytePacketBuilder -import kotlinx.io.core.readBytes -import kotlinx.io.core.writeFully -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.encodeToByteArray - -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.tools.hex2ByteArray -import moe.fuqiuluo.shamrock.tools.toHexString -import moe.fuqiuluo.shamrock.utils.DeflateTools -import moe.fuqiuluo.shamrock.utils.MD5 -import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher -import protobuf.fav.WeiyunAddRichMediaReq -import protobuf.fav.WeiyunAuthor -import protobuf.fav.WeiyunCollectCommInfo -import protobuf.fav.WeiyunComm -import protobuf.fav.WeiyunCommonReq -import protobuf.fav.WeiyunFastUploadResourceReq -import protobuf.fav.WeiyunGetFavContentReq -import protobuf.fav.WeiyunGetFavListReq -import protobuf.fav.WeiyunMsgHead -import protobuf.fav.WeiyunPicInfo -import protobuf.fav.WeiyunRichMediaContent -import protobuf.fav.WeiyunRichMediaSummary -import mqq.manager.TicketManager -import oicq.wlogin_sdk.request.Ticket -import oicq.wlogin_sdk.request.WtTicketPromise -import oicq.wlogin_sdk.tools.ErrMsg -import protobuf.auto.toByteArray -import java.io.ByteArrayOutputStream -import java.io.File -import java.nio.ByteBuffer -import kotlin.coroutines.resume - - -/** - * QQ收藏相关接口 - */ -internal object QFavSvc: BaseSvc() { - private val SERVER_LIST_COLLECTOR = listOf(ServerAddr().also { - it.isIpv6 = false - it.mIp = "collector.weiyun.com" - it.port = 80 - }) - private val SERVER_LIST_PICUP = listOf(ServerAddr().also { - it.isIpv6 = false - it.mIp = "pic.pieceup.qq.com" - it.port = 80 - }) - private const val VERSION = 12820 - private const val APPID = 30244 - private const val SUB_APPID = 538116905 - private const val MAJOR_VERSION = 8 - private const val MINOR_VERSION = 9 - private var seq = 1 - - suspend fun getItemList( - category: Int, - startPos: Int, - pageSize: Int, - ): Result { - val baseReq = WeiyunCommonReq( - getFavListReq = WeiyunGetFavListReq( - type = 0u, - bid = 0u, - category = category.toUInt(), - startTime = 0u, - orderType = 0u, - startPos = startPos.toUInt(), - pageSize = pageSize.toUInt(), - syncPolicy = 0u, - reqSource = 0u - ) - ) - return sendWeiyunReq(20000, baseReq) - } - - suspend fun getItemContent( - id: String - ): Result { - return sendWeiyunReq(20001, WeiyunCommonReq( - getFavContentReq = WeiyunGetFavContentReq( - cidList = arrayListOf(id) - ) - ) - ) - } - - suspend fun addImageMsg( - uin: Long, - name: String, - groupId: Long = 0, - groupName: String = "", - picUrl: String, - pid: String, - width: Int, height: Int, - size: Long, - md5: String, - ): Result { - val md5Bytes = md5.hex2ByteArray() - return sendWeiyunReq(20009, WeiyunCommonReq( - addRichMediaReq = WeiyunAddRichMediaReq( - commInfo = WeiyunCollectCommInfo( - bid = 1u, - category = 1u, - author = WeiyunAuthor( - type = if (groupId == 0L) 1u else 2u, - numId = uin.toULong(), - strId = name, - groupId = groupId.toULong(), - groupName = groupName - ), - createTime = System.currentTimeMillis().toULong() - 2000u, - seq = System.currentTimeMillis().toULong() - 1000u, - bizDataList = arrayListOf("""{"recordAudioOnly":false,"audioOnly":false,"fileOnly":false}""".toByteArray()), - originalAppId = 0u, - customGroupId = 0u - ), - summary = WeiyunRichMediaSummary( - title = "", - brief = "[图片]", - picList = arrayListOf( - WeiyunPicInfo( - uri = picUrl, - md5 = md5Bytes, - sha1 = md5.toByteArray(), - name = "", - note = "", - width = width.toUInt(), - height = height.toUInt(), - size = size.toULong(), - type = 0u, - picId = pid - ) - ), - contentType = 1u - ), - richMediaContent = listOf( - WeiyunRichMediaContent( - rawData = """""".toByteArray(), - picList = listOf( - WeiyunPicInfo( - uri = picUrl, - md5 = md5Bytes, - sha1 = md5.toByteArray(), - name = "", - note = "", - width = width.toUInt(), - height = height.toUInt(), - size = size.toULong(), - type = 0u, - picId = pid - ) - ) - ) - ) - ) - ) - ) - } - - suspend fun applyUpImageMsg( - uin: Long, - name: String, - groupId: Long = 0, - groupName: String = "", - width: Int, height: Int, - image: File - ): Result { - if (!image.exists()) { - return Result.failure(IllegalArgumentException("image file not exists")) - } - val md5 = MD5.genFileMd5(image.absolutePath) - return sendWeiyunReq(20010, WeiyunCommonReq( - fastUploadResourceReq = WeiyunFastUploadResourceReq( - picInfoList = listOf( - WeiyunPicInfo( - md5 = md5, - name = md5.toHexString(), - width = width.toUInt(), - height = height.toUInt(), - size = image.length().toULong(), - type = 1u, - picId = "/storage/emulated/0/DCIM/temp.jpeg", - owner = WeiyunAuthor( - type = if (groupId == 0L) 1u else 2u, - numId = uin.toULong(), - strId = name, - groupId = groupId.toULong(), - groupName = groupName - ) - ) - ), - ) - ) - ) - } - - suspend fun addRichMediaMsg( - uin: Long, - name: String, - groupId: Long = 0, - groupName: String = "", - time: Long = System.currentTimeMillis(), - content: String - ): Result { - return sendWeiyunReq(20009, WeiyunCommonReq( - addRichMediaReq = WeiyunAddRichMediaReq( - commInfo = WeiyunCollectCommInfo( - bid = 1u, - category = 1u, - author = WeiyunAuthor( - type = if (groupId == 0L) 1u else 2u, - numId = uin.toULong(), - strId = name, - groupId = groupId.toULong(), - groupName = groupName - ), - createTime = time.toULong() - 2000u, - seq = time.toULong() - 1000u, - originalAppId = 0u, - customGroupId = 0u - ), - summary = WeiyunRichMediaSummary( - brief = content, - contentType = 1u - ), - richMediaContent = listOf( - WeiyunRichMediaContent( - rawData = content.textToHtml().toByteArray(), - ) - ) - ) - ) - ) - } - - private fun String.textToHtml(): String { - return replace("\n", "

") - } - - suspend fun sendPicUpBlock( - fileSize: Long, - offset: Long, - block: ByteArray, - blockSize: Long, - sha: ByteArray, - pid: String, - outputStream: ByteArrayOutputStream = ByteArrayOutputStream(), - ): Result { - return suspendCancellableCoroutine { - val httpNetReq = HttpNetReq() - httpNetReq.userData = null - httpNetReq.mCallback = object: INetEngineListener { - override fun onResp(netResp: NetResp) { - if (netResp.mHttpCode != 200 && netResp.mResult != 0 && netResp.mErrDesc.isNullOrEmpty()) { - netResp.mErrDesc = netResp.mRespProperties["User-ErrMsg"] - } - netResp.mRespData = outputStream.toByteArray().copyOf() - it.resume(Result.success(netResp)) - } - - override fun onUpdateProgeress(netReq: NetReq, curr: Long, final: Long) {} - } - val vi = (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getA2(app.currentAccountUin) - //LogCenter.log(pSKey) - httpNetReq.mHttpMethod = HttpNetReq.HTTP_POST - httpNetReq.mSendData = BytePacketBuilder().apply { - writeInt(-1412589450) - writeInt(10000) - writeInt(0) - writeInt(sha.size + 16 + blockSize.toInt()) - writeShort(0) - writeShort(sha.size.toShort()) - writeFully(sha) - writeInt(fileSize.toInt()) - writeInt(offset.toInt()) - writeInt(blockSize.toInt()) - writeFully(block) - }.build().readBytes() - httpNetReq.mOutStream = outputStream - httpNetReq.mStartDownOffset = 0L - httpNetReq.mReqProperties["Shamrock"] = "true" - httpNetReq.mReqProperties["Cookie"] = String.format("uin=%s;vt=%d;vi=%s;pid=%s;appid=%d", app.currentAccountUin, 8, vi, pid, APPID) - httpNetReq.mReqProperties["host"] = "pic.pieceup.qq.com" - httpNetReq.mReqProperties["Range"] = "bytes=0-" - httpNetReq.mReqProperties["Content-Length"] = httpNetReq.mSendData.size.toString() - httpNetReq.mReqProperties["Accept-Encoding"] = "gzip" - httpNetReq.mReqProperties["Content-Encoding"] = "gzip" - httpNetReq.mPrioty = 1 - httpNetReq.mReqUrl = "https://pic.pieceup.qq.com/" - httpNetReq.mServerList = SERVER_LIST_PICUP - val service = AppRuntimeFetcher.appRuntime - .getRuntimeService(IHttpEngineService::class.java, "qqfav") - service.sendReq(httpNetReq) - } - } - - suspend fun sendWeiyunReq( - cmd: Int, - req: WeiyunCommonReq, - outputStream: ByteArrayOutputStream = ByteArrayOutputStream(), - ): Result { - return suspendCancellableCoroutine { - val httpNetReq = HttpNetReq() - httpNetReq.userData = null - httpNetReq.mCallback = object: INetEngineListener { - override fun onResp(netResp: NetResp) { - if (netResp.mHttpCode != 200 && netResp.mResult != 0 && netResp.mErrDesc.isNullOrEmpty()) { - netResp.mErrDesc = netResp.mRespProperties["User-ErrMsg"] - } - netResp.mRespData = outputStream.toByteArray().copyOf() - it.resume(Result.success(netResp)) - } - - override fun onUpdateProgeress(netReq: NetReq, curr: Long, final: Long) {} - } - val pSKey = getWeiYunPSKey() - httpNetReq.mHttpMethod = HttpNetReq.HTTP_POST - httpNetReq.mSendData = DeflateTools.gzip(packData(packHead(cmd, pSKey), WeiyunComm( - req = req - ).toByteArray())) - httpNetReq.mOutStream = outputStream - httpNetReq.mStartDownOffset = 0L - httpNetReq.mReqProperties["Shamrock"] = "true" - httpNetReq.mReqProperties["Cookie"] = String.format("uin=%s;vt=%d;vi=%s;appid=%d", app.currentAccountUin, 27, pSKey, APPID) - httpNetReq.mReqProperties["host"] = "collector.weiyun.com" - httpNetReq.mReqProperties["Range"] = "bytes=0-" - httpNetReq.mReqProperties["Content-Length"] = httpNetReq.mSendData.size.toString() - httpNetReq.mReqProperties["Accept-Encoding"] = "gzip" - httpNetReq.mReqProperties["Content-Encoding"] = "gzip" - httpNetReq.mPrioty = 1 - httpNetReq.mReqUrl = "https://collector.weiyun.com/collector.fcg" - httpNetReq.mServerList = SERVER_LIST_COLLECTOR - val service = AppRuntimeFetcher.appRuntime - .getRuntimeService(IHttpEngineService::class.java, "qqfav") - service.sendReq(httpNetReq) - } - } - - private fun packHead(cmd: Int, pskey: String): ByteArray { - return WeiyunMsgHead( - uin = app.longAccountUin.toULong(), - seq = seq++.toUInt(), - type = 1u, - cmd = cmd.toUInt(), - appId = APPID.toUInt(), - version = VERSION.toUInt(), - netType = 3u, - keyType = 27u, - key = pskey.toByteArray(), - majorVersion = MAJOR_VERSION.toUInt(), - minorVersion = MINOR_VERSION.toUInt(), - ).toByteArray() - } - - private fun packData(head: ByteArray, body: ByteArray): ByteArray { - val len = 16 + head.size + body.size - val buf = ByteBuffer.allocate(len) - buf.putInt(SUB_APPID) - buf.putShort(1) - buf.putInt(len) - buf.putInt(body.size) - buf.putShort(0) - buf.put(head) - buf.put(body) - return buf.array() - } - - private fun getWeiYunPSKey(): String { - val pskey = (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager) - .getPskey(app.currentAccountUin, 16L, arrayOf("weiyun.com"), WeiYunPSKeyPromise) - return if (pskey != null) pskey.getPSkey("weiyun.com") else "" - } - - private object WeiYunPSKeyPromise: WtTicketPromise { - override fun Done(ticket: Ticket) { - LogCenter.log("Fav: getPskeyPromise: done", Level.DEBUG) - } - - override fun Failed(errMsg: ErrMsg) { - LogCenter.log("Fav: getPskeyPromise: failed, $errMsg", Level.DEBUG) - } - - override fun Timeout(errMsg: ErrMsg) { - LogCenter.log("Fav: getPskeyPromise: timeout, $errMsg", Level.DEBUG) - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/QSafeSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/QSafeSvc.kt deleted file mode 100644 index 3f7e9f07..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/QSafeSvc.kt +++ /dev/null @@ -1,33 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet - -import QQService.SvcDevLoginInfo -import QQService.SvcReqGetDevLoginInfo -import QQService.SvcRspGetDevLoginInfo -import com.qq.jce.wup.UniPacket -import moe.fuqiuluo.qqinterface.servlet.BaseSvc -import mqq.app.MobileQQ -import mqq.app.Packet -import oicq.wlogin_sdk.tools.util - -internal object QSafeSvc: BaseSvc() { - - suspend fun getOnlineClients(): ArrayList? { - val req = SvcReqGetDevLoginInfo() - req.vecGuid = util.getGuidFromFile(MobileQQ.getContext()) - req.strAppName = MobileQQ.getMobileQQ().qqProcessName.split(":")[0] - req.iLoginType = 1 - req.iRequireMax = 20 - req.iGetDevListType = 6 - - val uniPacket = UniPacket() - uniPacket.servantName = "StatSvc" - uniPacket.funcName = "SvcReqGetDevLoginInfo" - uniPacket.put("SvcReqGetDevLoginInfo", req) - val resp = sendBufferAW("StatSvc.GetDevLoginInfo", false, uniPacket.encode()) - ?: return null - - return Packet.decodePacket(resp, "SvcRspGetDevLoginInfo", SvcRspGetDevLoginInfo()).vecCurrentLoginDevInfo - } - - -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/TicketSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/TicketSvc.kt deleted file mode 100644 index 415bf8f4..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/TicketSvc.kt +++ /dev/null @@ -1,179 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet - -import com.tencent.guild.api.transfile.IGuildTransFileApi -import com.tencent.mobileqq.app.QQAppInterface -import com.tencent.mobileqq.pskey.oidb.cmd0x102a.oidb_cmd0x102a -import com.tencent.mobileqq.qroute.QRoute -import io.ktor.client.request.get -import io.ktor.client.request.header -import moe.fuqiuluo.shamrock.remote.service.data.BigDataTicket -import moe.fuqiuluo.shamrock.tools.GlobalClientNoRedirect -import moe.fuqiuluo.shamrock.tools.slice -import mqq.app.MobileQQ -import mqq.manager.TicketManager -import oicq.wlogin_sdk.request.Ticket -import tencent.im.oidb.oidb_sso - -internal object TicketSvc: BaseSvc() { - object SigType { - const val WLOGIN_A5 = 2 - const val WLOGIN_RESERVED = 16 - const val WLOGIN_STWEB = 32 // TLV 103 - const val WLOGIN_A2 = 64 - const val WLOGIN_ST = 128 - const val WLOGIN_AQSIG = 2097152 - const val WLOGIN_D2 = 262144 - const val WLOGIN_DA2 = 33554432 - const val WLOGIN_LHSIG = 4194304 - const val WLOGIN_LSKEY = 512 - const val WLOGIN_OPENKEY = 16384 - const val WLOGIN_PAYTOKEN = 8388608 - const val WLOGIN_PF = 16777216 - const val WLOGIN_PSKEY = 1048576 - const val WLOGIN_PT4Token = 134217728 - const val WLOGIN_QRPUSH = 67108864 - const val WLOGIN_SID = 524288 - const val WLOGIN_SIG64 = 8192 - const val WLOGIN_SKEY = 4096 - const val WLOGIN_TOKEN = 32768 - const val WLOGIN_VKEY = 131072 - - val ALL_TICKET = arrayOf( - WLOGIN_A5, WLOGIN_RESERVED, WLOGIN_STWEB, WLOGIN_A2, WLOGIN_ST, WLOGIN_AQSIG, WLOGIN_D2, WLOGIN_DA2, - WLOGIN_LHSIG, WLOGIN_LSKEY, WLOGIN_OPENKEY, WLOGIN_PAYTOKEN, WLOGIN_PF, WLOGIN_PSKEY, WLOGIN_PT4Token, - WLOGIN_QRPUSH, WLOGIN_SID, WLOGIN_SIG64, WLOGIN_SKEY, WLOGIN_TOKEN, WLOGIN_VKEY - ) - } - - fun getUin(): String { - return app.currentUin.ifBlank { "0" } - } - - fun getLongUin(): Long { - return app.longAccountUin - } - - fun getUid(): String { - return app.currentUid.ifBlank { "u_" } - } - - fun getNickname(): String { - return app.currentNickname - } - - fun getCookie(): String { - val uin = getUin() - val skey = getRealSkey(uin) - val pskey = getPSKey(uin) - return "uin=o$uin; skey=$skey; p_uin=o$uin; p_skey=$pskey" - } - - suspend fun getCookie(domain: String): String { - val uin = getUin() - val skey = getRealSkey(uin) - val pskey = getPSKey(uin, domain) ?: "" - val pt4token = getPt4Token(uin, domain) ?: "" - return "uin=o$uin; skey=$skey; p_uin=o$uin; p_skey=$pskey; pt4_token=$pt4token" - } - - fun getBigdataTicket(): BigDataTicket? { - return runCatching { - QRoute.api(IGuildTransFileApi::class.java).bigDataTicket?.let { - BigDataTicket(it.getSessionKey(), it.getSessionSig()) - } - }.getOrNull() - } - - fun getCSRF(pskey: String = getPSKey(getUin())): String { - if (pskey.isEmpty()) { - return "0" - } - var v = 5381 - for (element in pskey) { - v += ((v shl 5) + element.code.toLong()).toInt() - } - return (v and Int.MAX_VALUE).toString() - } - - suspend fun getCSRF(uin: String, domain: String): String { - // 是不是要用Skey? - return getBkn(getPSKey(uin, domain) ?: getSKey(uin)) - } - - fun getBkn(arg: String): String { - var v: Long = 5381 - for (element in arg) { - v += (v shl 5 and 2147483647L) + element.code.toLong() - } - return (v and 2147483647L).toString() - } - - fun getTicket(uin: String, id: Int): Ticket? { - return (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getLocalTicket(uin, id) - } - - fun getStWeb(uin: String): String { - return (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getStweb(uin) - } - - fun getSKey(uin: String): String { - return (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getSkey(uin) - } - - fun getRealSkey(uin: String): String { - return (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getRealSkey(uin) - } - - fun getPSKey(uin: String): String { - val manager = (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager) - manager.reloadCache(MobileQQ.getContext()) - return manager.getSuperkey(uin) ?: "" - } - - suspend fun getLessPSKey(vararg domain: String): Result> { - val req = oidb_cmd0x102a.GetPSkeyRequest() - req.domains.set(domain.toList()) - val buffer = sendOidbAW("OidbSvcTcp.0x102a", 4138, 0, req.toByteArray()) - ?: return Result.failure(Exception("getLessPSKey failed")) - val body = oidb_sso.OIDBSSOPkg() - body.mergeFrom(buffer.slice(4)) - val rsp = oidb_cmd0x102a.GetPSkeyResponse().mergeFrom(body.bytes_bodybuffer.get().toByteArray()) - return Result.success(rsp.private_keys.get()) - } - - suspend fun getPSKey(uin: String, domain: String): String? { - return (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getPskey(uin, domain).let { - if (it.isNullOrBlank()) - getLessPSKey(domain).getOrNull()?.firstOrNull()?.key?.get() - else it - } - } - - fun getPt4Token(uin: String, domain: String): String? { - return (app.getManager(QQAppInterface.TICKET_MANAGER) as TicketManager).getPt4Token(uin, domain) - } - - suspend fun GetHttpCookies(appid: String, daid: String, jumpurl: String): String? { - val uin = getUin() - val clientkey = getStWeb(uin) - var url = "https://ui.ptlogin2.qq.com/cgi-bin/login?pt_hide_ad=1&style=9&appid=$appid&pt_no_auth=1&pt_wxtest=1&daid=$daid&s_url=$jumpurl" - var cookie = GlobalClientNoRedirect.get(url).headers.getAll("Set-Cookie")?.joinToString(";") - url = "https://ssl.ptlogin2.qq.com/jump?u1=$jumpurl&pt_report=1&daid=$daid&style=9&keyindex=19&clientuin=$uin&clientkey=$clientkey" - GlobalClientNoRedirect.get(url) { - header("Cookie", cookie) - }.let { - cookie = it.headers.getAll("Set-Cookie")?.joinToString(";") - url = it.headers["Location"].toString() - } - cookie = GlobalClientNoRedirect.get(url).headers.getAll("Set-Cookie")?.joinToString(";") - val extractedCookie = StringBuilder() - val cookies = cookie?.split(";") - cookies?.filter { cookie -> - val cookiePair = cookie.trim().split("=") - cookiePair.size == 2 && cookiePair[1].isNotBlank() && cookiePair[0].trim() in listOf("uin", "skey", "p_uin", "p_skey", "pt4_token") - }?.forEach { - extractedCookie.append("$it; ") - } - return extractedCookie.toString().trim() - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/VisitorSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/VisitorSvc.kt deleted file mode 100644 index 57f6dc56..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/VisitorSvc.kt +++ /dev/null @@ -1,95 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet - -internal object VisitorSvc: BaseSvc() { - const val FROM_C2C_AIO = 2 - const val FROM_CONDITION_SEARCH = 9 - const val FROM_CONTACTS_TAB = 5 - const val FROM_FACE_2_FACE_ADD_FRIEND = 11 - const val FROM_MAYKNOW_FRIEND = 3 - const val FROM_QCIRCLE = 4 - const val FROM_QQ_TROOP = 1 - const val FROM_QZONE = 7 - const val FROM_SCAN = 6 - const val FROM_SEARCH = 8 - const val FROM_SETTING_ME = 12 - const val FROM_SHARE_CARD = 10 - - const val IS_BLACK_LIST = "is_blacklist_user_profile" - const val PROFILE_CARD_IS_BLACK = 2 - const val PROFILE_CARD_IS_BLACKED = 1 - const val PROFILE_CARD_NOT_BLACK = 3 - - const val SUB_FROM_C2C_AIO = 21 - const val SUB_FROM_C2C_INTERACTIVE_LOGO = 25 - const val SUB_FROM_C2C_LEFT_SLIDE = 23 - const val SUB_FROM_C2C_OTHER = 24 - const val SUB_FROM_C2C_SETTING = 22 - const val SUB_FROM_C2C_TOFU = 26 - const val SUB_FROM_CONDITION_SEARCH_OTHER = 99 - const val SUB_FROM_CONDITION_SEARCH_RESULT = 91 - const val SUB_FROM_CONTACTS_FRIEND_TAB = 51 - const val SUB_FROM_CONTACTS_TAB = 55 - const val SUB_FROM_FACE_2_FACE_ADD_FRIEND_RESULT_AVATAR = 111 - const val SUB_FROM_FACE_2_FACE_OTHER = 119 - const val SUB_FROM_FRIEND_APPLY = 56 - const val SUB_FROM_FRIEND_NOTIFY_MORE = 57 - const val SUB_FROM_FRIEND_NOTIFY_TAB = 54 - const val SUB_FROM_GROUPING_TAB = 52 - const val SUB_FROM_MAYKNOW_FRIEND_CONTACT_TAB = 31 - const val SUB_FROM_MAYKNOW_FRIEND_CONTACT_TAB_MORE = 37 - const val SUB_FROM_MAYKNOW_FRIEND_FIND_PEOPLE = 34 - const val SUB_FROM_MAYKNOW_FRIEND_FIND_PEOPLE_MORE = 39 - const val SUB_FROM_MAYKNOW_FRIEND_FIND_PEOPLE_SEARCH = 36 - const val SUB_FROM_MAYKNOW_FRIEND_NEW_FRIEND_PAGE = 32 - const val SUB_FROM_MAYKNOW_FRIEND_OTHER = 35 - const val SUB_FROM_MAYKNOW_FRIEND_SEARCH = 33 - const val SUB_FROM_MAYKNOW_FRIEND_SEARCH_MORE = 38 - const val SUB_FROM_PHONE_LIST_TAB = 53 - const val SUB_FROM_QCIRCLE_OTHER = 42 - const val SUB_FROM_QCIRCLE_PROFILE = 41 - const val SUB_FROM_QQ_TROOP_ACTIVE_MEMBER = 15 - const val SUB_FROM_QQ_TROOP_ADMIN = 16 - const val SUB_FROM_QQ_TROOP_AIO = 11 - const val SUB_FROM_QQ_TROOP_MEMBER = 12 - const val SUB_FROM_QQ_TROOP_OTHER = 14 - const val SUB_FROM_QQ_TROOP_SETTING_MEMBER_LIST = 17 - const val SUB_FROM_QQ_TROOP_TEMP_SESSION = 13 - const val SUB_FROM_QRCODE_SCAN_DRAWER = 64 - const val SUB_FROM_QRCODE_SCAN_NEW = 61 - const val SUB_FROM_QRCODE_SCAN_OLD = 62 - const val SUB_FROM_QRCODE_SCAN_OTHER = 69 - const val SUB_FROM_QRCODE_SCAN_PROFILE = 63 - const val SUB_FROM_QZONE_HOME = 71 - const val SUB_FROM_QZONE_OTHER = 79 - const val SUB_FROM_SEARCH_CONTACT_TAB_MORE_FIND_PROFILE = 83 - const val SUB_FROM_SEARCH_FIND_PROFILE_TAB = 82 - const val SUB_FROM_SEARCH_MESSAGE_TAB_MORE_FIND_PROFILE = 84 - const val SUB_FROM_SEARCH_NEW_FRIEND_MORE_FIND_PROFILE = 85 - const val SUB_FROM_SEARCH_OTHER = 89 - const val SUB_FROM_SEARCH_TAB = 81 - const val SUB_FROM_SETTING_ME_AVATAR = 121 - const val SUB_FROM_SETTING_ME_OTHER = 129 - const val SUB_FROM_SHARE_CARD_C2C = 101 - const val SUB_FROM_SHARE_CARD_OTHER = 109 - const val SUB_FROM_SHARE_CARD_TROOP = 102 - const val SUB_FROM_TYPE_DEFAULT = 0 - - suspend fun vote(target: Long, count: Int): Result { - if(count !in 1 .. 20) { - return Result.failure(IllegalArgumentException("vote count must be in 1 .. 20")) - } - val card = CardSvc.getProfileCard(target).onFailure { - return Result.failure(RuntimeException("unable to fetch contact info")) - }.getOrThrow() - sendExtra("VisitorSvc.ReqFavorite") { - it.putLong(moe.fuqiuluo.shamrock.remote.service.data.profile.ProfileProtocolConst.PARAM_SELF_UIN, currentUin.toLong()) - it.putLong(moe.fuqiuluo.shamrock.remote.service.data.profile.ProfileProtocolConst.PARAM_TARGET_UIN, target) - it.putByteArray("vCookies", card.vCookies) - it.putBoolean("nearby_people", true) - it.putInt("favoriteSource", FROM_CONTACTS_TAB) - it.putInt("iCount", count) - it.putInt("from", FROM_CONTACTS_TAB) - } - return Result.success(Unit) - } -} diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/ArkMsgSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/ArkMsgSvc.kt deleted file mode 100644 index 0a3c47d1..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/ArkMsgSvc.kt +++ /dev/null @@ -1,94 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet.ark - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import moe.fuqiuluo.qqinterface.servlet.BaseSvc -import moe.fuqiuluo.qqinterface.servlet.ark.data.ArkAppInfo -import tencent.im.oidb.cmd0xb77.oidb_cmd0xb77 - -internal object ArkMsgSvc: BaseSvc() { - fun tryShareMusic( - chatType: Int, - peerId: Long, - msgId: Long, - arkAppInfo: ArkAppInfo, - title: String, - singer: String, - jumpUrl: String, - previewUrl: String, - musicUrl: String, - ) { - val req = oidb_cmd0xb77.ReqBody() - req.appid.set(arkAppInfo.appId) - req.app_type.set(1) - req.msg_style.set(4) - req.client_info.set(oidb_cmd0xb77.ClientInfo().also { - it.platform.set(1) - it.sdk_version.set(arkAppInfo.version) - it.android_package_name.set(arkAppInfo.packageName) - it.android_signature.set(arkAppInfo.signature) - }) - req.ext_info.set(oidb_cmd0xb77.ExtInfo().also { - it.msg_seq.set(msgId) - }) - req.recv_uin.set(peerId) - req.rich_msg_body.set(oidb_cmd0xb77.RichMsgBody().also { - it.title.set(title) - it.summary.set(singer) - it.url.set(jumpUrl) - it.picture_url.set(previewUrl) - it.music_url.set(musicUrl) - }) - when (chatType) { - MsgConstant.KCHATTYPEGROUP -> req.send_type.set(1) - MsgConstant.KCHATTYPEC2C -> req.send_type.set(0) - else -> error("不支持该聊天类型发送音乐分享") - } - sendOidb("OidbSvc.0xb77_9", 0xb77, 9, req.toByteArray()) - } - - /* - suspend fun tryShareJsonMessage( - jsonString: String, - arkAppInfo: ArkAppInfo = ArkAppInfo.DanMaKu, - ): Result { - val msgSeq = MessageHelper.generateMsgId(MsgConstant.KCHATTYPEC2C).qqMsgId - val req = oidb_cmd0xb77.ReqBody() - req.appid.set(arkAppInfo.appId) - req.app_type.set(1) - req.msg_style.set(10) - req.client_info.set(oidb_cmd0xb77.ClientInfo().also { - it.platform.set(1) - it.sdk_version.set(arkAppInfo.version) - it.android_package_name.set(arkAppInfo.packageName) - it.android_signature.set(arkAppInfo.signature) - }) - req.ext_info.set(oidb_cmd0xb77.ExtInfo().also { - it.tag_name.set(ByteStringMicro.copyFromUtf8("shamrock")) - it.msg_seq.set(msgSeq) - }) - req.send_type.set(0) - req.recv_uin.set(TicketSvc.getLongUin()) - req.mini_app_msg_body.set(oidb_cmd0xb77.MiniAppMsgBody().also { - it.mini_app_appid.set(arkAppInfo.miniAppId) - it.mini_app_path.set("pages") - it.web_page_url.set("https://im.qq.com/index/") - it.title.set("title") - it.desc.set("desc") - it.json_str.set(jsonString) - }) - sendOidb("OidbSvc.0xb77_9", 0xb77, 9, req.toByteArray()) - val signedJson: String = withTimeoutOrNull(5.seconds) { - suspendCancellableCoroutine { - AioListener.registerTemporaryMsgListener(msgSeq) { - it.resume(elements.first { - it.elementType == MsgConstant.KELEMTYPEARKSTRUCT - }.arkElement.bytesData) - } - it.invokeOnCancellation { - AioListener.unregisterTemporaryMsgListener(msgSeq) - } - } - } ?: return Result.failure(Exception("unable to sign json")) - return Result.success(signedJson) - }*/ -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/LightAppSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/LightAppSvc.kt deleted file mode 100644 index 6f35f458..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/LightAppSvc.kt +++ /dev/null @@ -1,45 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet.ark - -import moe.fuqiuluo.qqinterface.servlet.BaseSvc -import moe.fuqiuluo.qqinterface.servlet.ark.data.ArkAppInfo -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import moe.fuqiuluo.symbols.decodeProtobuf -import protobuf.auto.toByteArray -import protobuf.lightapp.AdaptShareInfoReq -import protobuf.lightapp.AdaptShareInfoResp -import protobuf.qweb.DEFAULT_DEVICE_INFO -import protobuf.qweb.QWebReq -import protobuf.qweb.QWebRsp - -internal object LightAppSvc: BaseSvc() { - suspend fun adaptShareJumpUrl( - arkAppInfo: ArkAppInfo, - coverUrl: String, - desc: String, - url: String - ): Result { - val rsp = sendBufferAW("LightAppSvc.mini_app_share.AdaptShareInfo", true, QWebReq( - seq = 10, - qua = PlatformUtils.getQUA(), - deviceInfo = DEFAULT_DEVICE_INFO, - buffer = AdaptShareInfoReq( - appid = arkAppInfo.miniAppId.toString(), - title = arkAppInfo.appName, - desc = desc, - time = (System.currentTimeMillis() * 0.001).toULong(), - scene = 3u, - templetType = 1u, - businessType = 0u, - picUrl = coverUrl, - jumpUrl = "pages", - verType = 3u, - withShareTicket = 0u, - webURL = url, - ).toByteArray(), - traceId = app.account + "_0_0", - ).toByteArray())?.decodeProtobuf()?.buffer?.decodeProtobuf() - if (rsp == null || rsp.json.isNullOrEmpty()) - return Result.failure(Exception("unable to adapt ShareInfo")) - return Result.success(rsp.json!!) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/WeatherSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/WeatherSvc.kt deleted file mode 100644 index 92ee5620..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/WeatherSvc.kt +++ /dev/null @@ -1,74 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet.ark - -import io.ktor.client.request.get -import io.ktor.client.request.header -import io.ktor.client.request.url -import io.ktor.client.statement.bodyAsText -import io.ktor.http.HttpStatusCode -import io.ktor.http.encodeURLQueryComponent -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonObject -import moe.fuqiuluo.qqinterface.servlet.TicketSvc -import moe.fuqiuluo.qqinterface.servlet.ark.data.Region -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.tools.* -import java.lang.Exception - -internal object WeatherSvc { - suspend fun fetchWeatherCard(code: Int): Result { - val cookie = TicketSvc.getCookie("mp.qq.com") - val resp = GlobalClient.get("https://weather.mp.qq.com/page/poster?_wv=2&&_wwv=4&adcode=$code") { - header("Cookie", cookie) - } - - if (resp.status != HttpStatusCode.OK) { - LogCenter.log("fetchWeatherCard: error: ${resp.status}, cookie: $cookie", Level.ERROR) - return Result.failure(Exception("search city failed")) - } - - val textJson = resp.bodyAsText() - .replace("\n", "") - .split("window.__INITIAL_STATE__ =")[1] - .split("};")[0].trim() + "}" - - //LogCenter.log(textJson) - - return Result.success(Json.parseToJsonElement(textJson).asJsonObject) - } - - suspend fun searchCity(query: String): Result> { - val pskey = TicketSvc.getPSKey(TicketSvc.getUin(), "mp.qq.com") ?: "" - val cookie = TicketSvc.getCookie("mp.qq.com") - val gtk = TicketSvc.getCSRF(pskey) - val resp = GlobalClient.get { - url("https://weather.mp.qq.com/trpc/weather/SearchRegions?g_tk=$gtk&key=${query.encodeURLQueryComponent()}&offset=0&count=25") - header("Cookie", cookie) - } - - if (resp.status != HttpStatusCode.OK) { - LogCenter.log("GetWeatherCityCode: error: ${resp.status}, cookie: $cookie, bkn: $gtk", Level.ERROR) - return Result.failure(Exception("search city failed")) - } - - val json = GlobalJson.parseToJsonElement(resp.bodyAsText()).asJsonObject - - - val cnt = json["totalCount"].asInt - if (cnt == 0) { - return Result.success(emptyList()) - } - - val regions = json["regions"].asJsonArray.map { - val region = it.asJsonObject - Region( - region["adcode"].asInt, - region["province"].asStringOrNull, - region["city"].asStringOrNull - ) - } - - return Result.success(regions) - } - -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/data/ArkAppInfo.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/data/ArkAppInfo.kt deleted file mode 100644 index 8b950ef5..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/data/ArkAppInfo.kt +++ /dev/null @@ -1,40 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet.ark.data - -sealed class ArkAppInfo( - val appId: Long, - val version: String, - val packageName: String, - val signature: String, - val miniAppId: Long = 0, - val appName: String = "" -) { - data object QQMusic: ArkAppInfo( - appId = 100497308, - version = "0.0.0", - packageName = "com.tencent.qqmusic", - signature = "cbd27cd7c861227d013a25b2d10f0799" - ) - data object NetEaseMusic: ArkAppInfo( - appId = 100495085, - version = "0.0.0", - packageName = "com.netease.cloudmusic", - signature = "da6b069da1e2982db3e386233f68d76d" - ) - - data object DanMaKu: ArkAppInfo( - appId = 100951776, - version = "0.0.0", - packageName = "tv.danmaku.bili", - signature = "7194d531cbe7960a22007b9f6bdaa38b", - miniAppId = 1109937557, - appName = "哔哩哔哩" - ) - - data object Docs: ArkAppInfo( - appId = 0, - version = "0.0.0", - packageName = "", - signature = "f3da3147654d9a21f3237b88f20dce9c", - miniAppId = 1108338344 - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/data/Region.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/data/Region.kt deleted file mode 100644 index 6f96120b..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/data/Region.kt +++ /dev/null @@ -1,10 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet.ark.data - -import kotlinx.serialization.Serializable - -@Serializable -internal data class Region( - val adcode: Int, - val province: String?, - val city: String? -) \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/Converter.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/Converter.kt deleted file mode 100644 index 9f647ac7..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/Converter.kt +++ /dev/null @@ -1,112 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet.msg - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import com.tencent.qqnt.kernel.nativeinterface.MsgElement -import moe.fuqiuluo.qqinterface.servlet.msg.converter.ElemConverter -import moe.fuqiuluo.qqinterface.servlet.msg.converter.NtMsgElementConverter -import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.tools.toHexString -import protobuf.message.Elem -import protobuf.message.RichText - -@JvmName("richTextToSegments") -internal suspend fun RichText.toSegments( - chatType: Int, - peerId: String, - subPeer: String -): List { - val messageData = arrayListOf() - if (ptt != null) { - val md5 = ptt!!.fileMd5!! - messageData.add( - MessageSegment( - "record", mapOf( - "file" to md5.toHexString(), - "url" to when (chatType) { - MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPttDownUrl("0", ptt!!.fileUuid!!) - MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupPttDownUrl( - "0", - md5, - ptt!!.groupFileKey!! - ) - - else -> throw UnsupportedOperationException("Not supported chat type: $chatType") - }, - "magic" to ptt!!.pbReserve?.magic, - ) - ) - ) - } - elements?.forEach { msg -> - kotlin.runCatching { - val elementType = if (msg.text != null) { - 1 - } else if (msg.face != null) { - 2 - } else if (msg.notOnlineImage != null) { - 4 - } else if (msg.customFace != null) { - 8 - } else if (msg.generalFlags != null) { - 37 - } else if (msg.srcMsg != null) { - 45 - } else if (msg.lightApp != null) { - 51 - } else if (msg.commonElem != null) { - 53 - } else - throw UnsupportedOperationException("不支持的消息element类型:$msg") - val converter = ElemConverter[elementType] - converter?.invoke(chatType, peerId, subPeer, msg) - ?: throw UnsupportedOperationException("不支持的消息element类型:$elementType") - }.onSuccess { - messageData.add(it) - }.onFailure { - if (it is UnknownError) { - // 不处理的消息类型,抛出unknown error - } else { - LogCenter.log("消息element转换错误:$it", Level.WARN) - } - } - } - return messageData -} - -@JvmName("msgElementListToSegments") -internal suspend fun List.toSegments(chatType: Int, peerId: String, subPeer: String): List { - val messageData = arrayListOf() - this.forEach { msg -> - kotlin.runCatching { - val converter = NtMsgElementConverter[msg.elementType] - converter?.invoke(chatType, peerId, subPeer, msg) - ?: throw UnsupportedOperationException("不支持的消息element类型:${msg.elementType}") - }.onSuccess { - messageData.add(it) - }.onFailure { - if (it is UnknownError) { - // 不处理的消息类型,抛出unknown error - } else { - LogCenter.log("消息element转换错误:$it, elementType: ${msg.elementType}", Level.WARN) - } - } - } - return messageData -} - -internal suspend fun List.toCQCode(chatType: Int, peerId: String, subPeer: String): String { - if (this.isEmpty()) { - return "" - } - return MessageHelper.nativeEncodeCQCode(this.toSegments(chatType, peerId, subPeer).map { - val params = hashMapOf() - params["_type"] = it.type - it.data.forEach { (key, value) -> - params[key] = value.toString() - } - params - }) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/MessageSegment.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/MessageSegment.kt deleted file mode 100644 index 9f5fd5c6..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/MessageSegment.kt +++ /dev/null @@ -1,34 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet.msg - -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonObject -import moe.fuqiuluo.shamrock.tools.json - - -internal data class MessageSegment( - val type: String, - val data: Map = emptyMap() -) { - fun toJson(): JsonObject { - return mapOf( - "type" to type, - "data" to data - ).json - } -} - -internal fun List.toJson(): JsonArray { - return this.map { - it.toJson() - }.json -} - -internal fun List.toListMap(): List> { - return this.map { - mapOf( - "type" to it.type.json, - "data" to it.data.json - ) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/MessageTempHandler.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/MessageTempHandler.kt deleted file mode 100644 index c2f16dda..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/MessageTempHandler.kt +++ /dev/null @@ -1,34 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet.msg - -import com.tencent.qqnt.kernel.nativeinterface.MsgRecord -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import java.util.Collections - -internal object MessageTempHandler { - // 通过MSG SEQ临时监听器 - private val tempMessageListenerMap = Collections.synchronizedMap(HashMap Unit>()) - - fun registerTemporaryMsgListener( - msgSeq: Long, - listener: suspend MsgRecord.() -> Unit - ) { - LogCenter.log({ "注册临时消息监听器: $msgSeq" }, Level.DEBUG) - tempMessageListenerMap[msgSeq] = listener - } - - fun unregisterTemporaryMsgListener(msgSeq: Long) { - tempMessageListenerMap.remove(msgSeq) - } - - suspend fun notify(record: MsgRecord): Boolean { - tempMessageListenerMap.firstNotNullOfOrNull { - if (it.key == record.msgSeq) it else null - }?.let { - it.value(record) - tempMessageListenerMap.remove(it.key) - return true - } - return false - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/converter/ElemConverter.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/converter/ElemConverter.kt deleted file mode 100644 index 2bdf647c..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/converter/ElemConverter.kt +++ /dev/null @@ -1,593 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet.msg.converter - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import io.ktor.util.* -import kotlinx.io.core.ByteReadPacket -import kotlinx.io.core.discardExact -import kotlinx.io.core.readUInt -import moe.fuqiuluo.qqinterface.servlet.msg.MessageSegment -import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc -import moe.fuqiuluo.shamrock.helper.MessageHelper -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.helper.db.MessageDB -import moe.fuqiuluo.shamrock.tools.asJsonArray -import moe.fuqiuluo.shamrock.utils.DeflateTools -import moe.fuqiuluo.shamrock.tools.asJsonObject -import moe.fuqiuluo.shamrock.tools.asString -import moe.fuqiuluo.shamrock.tools.toHexString -import moe.fuqiuluo.symbols.decodeProtobuf -import moe.fuqiuluo.shamrock.tools.slice -import protobuf.message.Elem -import protobuf.message.element.commelem.ButtonExtra -import protobuf.message.element.commelem.MarkdownExtra -import protobuf.message.element.commelem.QFaceExtra - - -internal typealias IElemConverter = suspend (Int, String, String, Elem) -> MessageSegment - -internal object ElemConverter { - private val convertMap = mapOf( - 1 to ElemConverter::convertTextElem, - 2 to ElemConverter::convertFaceElem, - 4 to ElemConverter::convertNotOnlineImageElem, - 8 to ElemConverter::convertCustomFaceElem, -// MsgConstant.KELEMTYPEPTT to ElemConverter::convertVoiceElem, -// MsgConstant.KELEMTYPEVIDEO to ElemConverter::convertVideoElem, -// MsgConstant.KELEMTYPEMARKETFACE to ElemConverter::convertMarketFaceElem, - 37 to ElemConverter::convertGeneralFlagsElem, - 45 to ElemConverter::convertReplyElem, - 51 to ElemConverter::convertStructJsonElem, - 53 to ElemConverter::convertCommonElem, -// MsgConstant.KELEMTYPEGRAYTIP to ElemConverter::convertGrayTipsElem, -// MsgConstant.KELEMTYPEFILE to ElemConverter::convertFileElem, -// //MsgConstant.KELEMTYPEMULTIFORWARD to ElemConverter::convertXmlMultiMsgElem, -// //MsgConstant.KELEMTYPESTRUCTLONGMSG to ElemConverter::convertXmlLongMsgElem, -// MsgConstant.KELEMTYPEFACEBUBBLE to ElemConverter::convertBubbleFaceElem, - ) - - operator fun get(type: Int): IElemConverter? = convertMap[type] - - /** - * 文本 / 艾特 消息转换消息段 - */ - private suspend fun convertTextElem( - chatType: Int, - peerId: String, - subPeer: String, - element: Elem - ): MessageSegment { - val text = element.text!! - if (text.attr6Buf != null) { - val at = ByteReadPacket(text.attr6Buf!!) - at.discardExact(7) - val uin = at.readUInt() - return MessageSegment( - type = "at", - data = mapOf( - "qq" to uin - ) - ) - } else { - return MessageSegment( - type = "text", - data = mapOf( - "text" to text.str!! - ) - ) - } - } - - /** - * 小表情 / 戳一戳 消息转换消息段 - */ - private suspend fun convertFaceElem( - chatType: Int, - peerId: String, - subPeer: String, - element: Elem - ): MessageSegment { - val face = element.face!! - return MessageSegment( - type = "face", - data = mapOf( - "id" to face.index!! - ) - ) - - } - - /** - * 图片消息转换消息段 - */ - private suspend fun convertCustomFaceElem( - chatType: Int, - peerId: String, - subPeer: String, - element: Elem - ): MessageSegment { - val customFace = element.customFace!! - - val md5 = customFace.md5.toHexString() - - ImageDB.getInstance().imageMappingDao().insert( - ImageMapping( - fileName = md5.uppercase(), - md5 = md5.uppercase(), - chatType = chatType, - size = customFace.size!!.toLong(), - sha = "", - fileId = "", - storeId = 0, - ) - ) - - val origUrl = customFace.origUrl!! - - return MessageSegment( - type = "image", - data = mapOf( - "file" to md5, - "url" to when (chatType) { - MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl( - origUrl, - md5 - ) - - MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(origUrl, md5) - MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(origUrl, md5) - else -> throw UnsupportedOperationException("Not supported chat type: $chatType") - }, - "type" to if (customFace.origin == true) "original" else "show" - ) - ) - } - - private suspend fun convertNotOnlineImageElem( - chatType: Int, - peerId: String, - subPeer: String, - element: Elem - ): MessageSegment { - val notOnlineImage = element.notOnlineImage!! - - val md5 = notOnlineImage.picMd5.toHexString() - - ImageDB.getInstance().imageMappingDao().insert( - ImageMapping( - fileName = md5.uppercase(), - md5 = md5.uppercase(), - chatType = chatType, - size = notOnlineImage.fileLen!!.toLong(), - sha = "", - fileId = "", - storeId = 0, - ) - ) - - val origUrl = notOnlineImage.origUrl!! - - return MessageSegment( - type = "image", - data = mapOf( - "file" to md5, - "url" to when (chatType) { - MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl( - origUrl, - md5 - ) - - MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(origUrl, md5) - MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(origUrl, md5) - else -> throw UnsupportedOperationException("Not supported chat type: $chatType") - }, - "type" to if (notOnlineImage.original == true) "original" else "show" - ) - ) - } - - private suspend fun convertGeneralFlagsElem( - chatType: Int, - peerId: String, - subPeer: String, - element: Elem - ): MessageSegment { - val generalFlags = element.generalFlags!! - if (generalFlags.longTextFlag == 1u) { - return MessageSegment( - type = "general_flags", - data = mapOf( - "res_id" to generalFlags.longTextResid - ) - ) - } - throw UnknownError("no segment") - } - -// /** -// * 视频消息转换消息段 -// */ -// private suspend fun convertVideoElem( -// chatType: Int, -// peerId: String, -// subPeer: String, -// element: Elem -// ): MessageSegment { -// val video = element.videoElement -// 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() -// -// //LogCenter.log({ "receive video msg: $video" }, Level.DEBUG) -// -// return MessageSegment( -// type = "video", -// data = mapOf( -// "file" to video.fileName, -// "url" to when (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: $chatType") -// } -// ).also { -// if ((it["url"] as String).isBlank()) -// it.remove("url") -// } -// ) -// } -// -// /** -// * 商城大表情消息转换消息段 -// */ -// private suspend fun convertMarketFaceElem( -// chatType: Int, -// peerId: String, -// subPeer: String, -// element: Elem -// ): MessageSegment { -// val face = element.marketFaceElement -// return when (face.emojiId.lowercase()) { -// "4823d3adb15df08014ce5d6796b76ee1" -> MessageSegment("dice") -// "83c8a293ae65ca140f348120a77448ee" -> MessageSegment("rps") -// else -> MessageSegment( -// type = "mface", -// data = mapOf( -// "id" to face.emojiId -// ) -// ) -// } -// } -// - /** - * 回复消息转消息段 - */ - private suspend fun convertReplyElem( - chatType: Int, - peerId: String, - subPeer: String, - element: Elem - ): MessageSegment { - val srcMsg = element.srcMsg!! - val msgId = srcMsg.pbReserve?.msgRand?.toLong() ?: 0 - val msgHash = if (msgId != 0L) { - MessageHelper.generateMsgIdHash(chatType, msgId) - } else { - val msgSeq = srcMsg.origSeqs?.first()?.toInt() ?: 0 - MessageDB.getInstance().messageMappingDao() - .queryByMsgSeq(chatType, peerId, msgSeq)?.msgHashId - ?: kotlin.run { - LogCenter.log("消息映射关系未找到: Message($msgSeq)", Level.WARN) - MessageHelper.generateMsgIdHash(chatType, msgId) - } - } - - return MessageSegment( - type = "reply", - data = mapOf( - "id" to msgHash - ) - ) - } - - /** - * JSON消息转消息段 - */ - private suspend fun convertStructJsonElem( - chatType: Int, - peerId: String, - subPeer: String, - element: Elem - ): MessageSegment { - val data = element.lightApp!!.data!! - val jsonStr = String(if (data[0].toInt() == 1) DeflateTools.uncompress(data.slice(1)) else data.slice(1)) - val json = jsonStr.asJsonObject - return when (json["app"].asString) { - "com.tencent.multimsg" -> { - val info = json["meta"].asJsonObject["detail"].asJsonObject - MessageSegment( - type = "forward", - data = mapOf( - "id" to info["resid"].asString, - "filename" to info["uniseq"].asString, - "summary" to info["summary"].asString, - "desc" to info["news"].asJsonArray.joinToString("\n") { it.asJsonObject["text"].asString } - ) - ) - } - - "com.tencent.troopsharecard" -> { - val info = json["meta"].asJsonObject["contact"].asJsonObject - MessageSegment( - type = "contact", - data = mapOf( - "type" to "group", - "id" to info["jumpUrl"].asString.split("group_code=")[1] - ) - ) - } - - "com.tencent.contact.lua" -> { - val info = json["meta"].asJsonObject["contact"].asJsonObject - MessageSegment( - type = "contact", - data = mapOf( - "type" to "private", - "id" to info["jumpUrl"].asString.split("uin=")[1] - ) - ) - } - - "com.tencent.map" -> { - val info = json["meta"].asJsonObject["Location.Search"].asJsonObject - MessageSegment( - type = "location", - data = mapOf( - "lat" to info["lat"].asString, - "lon" to info["lng"].asString, - "content" to info["address"].asString, - "title" to info["name"].asString - ) - ) - } - - else -> MessageSegment( - type = "json", - data = mapOf( - "data" to jsonStr - ) - ) - } - } - - - private suspend fun convertCommonElem( - chatType: Int, - peerId: String, - subPeer: String, - element: Elem - ): MessageSegment { - val commonElem = element.commonElem!! - return when (commonElem.serviceType) { - - 37 -> { - val qFaceExtra = commonElem.elem!!.decodeProtobuf() - when (qFaceExtra.faceId) { - 358 -> MessageSegment( - type = "dice", - data = mapOf( - "result" to qFaceExtra.result!! - ) - ) - - 359 -> MessageSegment( - type = "rps", - data = mapOf( - "result" to qFaceExtra.result!! - ) - ) - - else -> MessageSegment( - type = "face", - data = mapOf( - "id" to qFaceExtra.faceId!!, - "big" to true, - "result" to qFaceExtra.result!! // (1布 2剪 3锤) (骰子123456) - ) - ) - } - } - - 45 -> { - val markdownExtra = commonElem.elem!!.decodeProtobuf() - MessageSegment( - type = "markdown", - data = mapOf( - "content" to markdownExtra.content!! - ) - ) - } - - 46 -> { - val buttonExtra = commonElem.elem!!.decodeProtobuf() - MessageSegment( - type = "button", - data = buttonExtra.field1!!.let { - mapOf( - "rows" to it.rows!!.map { row -> - mapOf( - "buttons" to row.buttons!!.map { button -> - val renderData = button.renderData - val action = button.action - val permission = action?.permission - mapOf( - "id" to button.id, - "render_data" to mapOf( - "label" to (renderData?.label ?: ""), - "visited_label" to (renderData?.visitedLabel ?: ""), - "style" to (renderData?.style ?: 0) - ), - "action" to mapOf( - "type" to (action?.type ?: 0), - "permission" to mapOf( - "type" to (permission?.type ?: 0), - "specify_role_ids" to permission?.specifyRoleIds, - "specify_user_ids" to permission?.specifyUserIds - ), - "unsupport_tips" to (action?.unsupportTips ?: ""), - "data" to (action?.data ?: ""), - "reply" to action?.reply, - "enter" to action?.enter, - ) - ) - }) - }, - "appid" to it.appid - ) - } - ) - } - - else -> MessageSegment( - type = "common", - data = mapOf( - "data" to commonElem.elem!!.encodeBase64() - ) - ) - } - } - - -// -// /** -// * 灰色提示条消息过滤 -// */ -// private suspend fun convertGrayTipsElem( -// chatType: Int, -// peerId: String, -// subPeer: String, -// element: Elem -// ): MessageSegment { -// val tip = element.grayTipElement -// when (tip.subElementType) { -// MsgConstant.GRAYTIPELEMENTSUBTYPEJSON -> { -// val notify = tip.jsonGrayTipElement -// when (notify.busiId) { -// /* 新人入群 */ 17L, /* 群戳一戳 */1061L, -// /* 群撤回 */1014L, /* 群设精消息 */2401L, -// /* 群头衔 */2407L -> { -// } -// -// else -> LogCenter.log("不支持的灰条类型(JSON): ${notify.busiId}", Level.WARN) -// } -// } -// -// MsgConstant.GRAYTIPELEMENTSUBTYPEXMLMSG -> { -// val notify = tip.xmlElement -// when (notify.busiId) { -// /* 群戳一戳 */1061L, /* 群打卡 */1068L -> {} -// else -> LogCenter.log("不支持的灰条类型(XML): ${notify.busiId}", Level.WARN) -// } -// } -// -// else -> LogCenter.log("不支持的提示类型: ${tip.subElementType}", Level.WARN) -// } -// // 提示类消息,这里提供的是一个xml,不具备解析通用性 -// // 在这里不推送 -// throw UnknownError() -// } -// -// /** -// * 文件消息转换消息段 -// */ -// private suspend fun convertFileElem( -// chatType: Int, -// peerId: String, -// subPeer: String, -// element: Elem -// ): MessageSegment { -// 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 (chatType) { -// MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId) -// MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildFileDownUrl(peerId, subPeer, fileId, bizId) -// else -> RichProtoSvc.getGroupFileDownUrl(peerId.toLong(), fileId, bizId) -// } -// -// return MessageSegment( -// type = "file", -// data = mapOf( -// "name" to fileName, -// "size" to fileSize, -// "expire" to expireTime, -// "id" to fileId, -// "url" to url, -// "biz" to bizId, -// "sub" to fileSubId -// ) -// ) -// } -// -// /** -// * 老板QQ的合并转发信息 -// */ -// private suspend fun convertXmlMultiMsgElem( -// chatType: Int, -// peerId: String, -// subPeer: String, -// element: Elem -// ): MessageSegment { -// val multiMsg = element.multiForwardElem -// return MessageSegment( -// type = "forward", -// data = mapOf( -// "id" to multiMsg.resId -// ) -// ) -// } -// -// private suspend fun convertXmlLongMsgElem( -// chatType: Int, -// peerId: String, -// subPeer: String, -// element: Elem -// ): MessageSegment { -// val longMsg = element.structLongElem -// return MessageSegment( -// type = "forward", -// data = mapOf( -// "id" to longMsg.resId -// ) -// ) -// } -// -// -// private suspend fun convertBubbleFaceElem( -// chatType: Int, -// peerId: String, -// subPeer: String, -// element: Elem -// ): MessageSegment { -// val bubbleElement = element.faceBubbleElement -// return MessageSegment( -// type = "bubble_face", -// data = mapOf( -// "id" to bubbleElement.yellowFaceInfo.index, -// "count" to (bubbleElement.faceCount ?: 1), -// ) -// ) -// } - - -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/converter/NtMsgElementConverter.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/converter/NtMsgElementConverter.kt deleted file mode 100644 index 2cff2c66..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/converter/NtMsgElementConverter.kt +++ /dev/null @@ -1,608 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet.msg.converter - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import com.tencent.qqnt.kernel.nativeinterface.MsgElement -import moe.fuqiuluo.qqinterface.servlet.msg.MessageSegment -import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc -import moe.fuqiuluo.shamrock.helper.ContactHelper -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.helper.db.ImageDB -import moe.fuqiuluo.shamrock.helper.db.ImageMapping -import moe.fuqiuluo.shamrock.helper.db.MessageDB -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.utils.PlatformUtils -import moe.fuqiuluo.shamrock.utils.PlatformUtils.QQ_9_0_8_VER - -internal typealias IMsgElementConverter = suspend (Int, String, String, MsgElement) -> MessageSegment - -internal object NtMsgElementConverter { - private val convertMap = hashMapOf( - MsgConstant.KELEMTYPETEXT to NtMsgElementConverter::convertTextElem, - MsgConstant.KELEMTYPEFACE to NtMsgElementConverter::convertFaceElem, - MsgConstant.KELEMTYPEPIC to NtMsgElementConverter::convertImageElem, - MsgConstant.KELEMTYPEPTT to NtMsgElementConverter::convertVoiceElem, - MsgConstant.KELEMTYPEVIDEO to NtMsgElementConverter::convertVideoElem, - MsgConstant.KELEMTYPEMARKETFACE to NtMsgElementConverter::convertMarketFaceElem, - MsgConstant.KELEMTYPEARKSTRUCT to NtMsgElementConverter::convertStructJsonElem, - MsgConstant.KELEMTYPEREPLY to NtMsgElementConverter::convertReplyElem, - MsgConstant.KELEMTYPEGRAYTIP to NtMsgElementConverter::convertGrayTipsElem, - MsgConstant.KELEMTYPEFILE to NtMsgElementConverter::convertFileElem, - MsgConstant.KELEMTYPEMARKDOWN to NtMsgElementConverter::convertMarkdownElem, - //MsgConstant.KELEMTYPEMULTIFORWARD to MsgElementConverter::convertXmlMultiMsgElem, - //MsgConstant.KELEMTYPESTRUCTLONGMSG to MsgElementConverter::convertXmlLongMsgElem, - MsgConstant.KELEMTYPEFACEBUBBLE to NtMsgElementConverter::convertBubbleFaceElem, - MsgConstant.KELEMTYPEINLINEKEYBOARD to NtMsgElementConverter::convertInlineKeyboardElem - ) - - operator fun get(type: Int): IMsgElementConverter? = convertMap[type] - - /** - * 文本 / 艾特 消息转换消息段 - */ - private suspend fun convertTextElem( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val text = element.textElement - return if (text.atType != MsgConstant.ATTYPEUNKNOWN) { - MessageSegment( - type = "at", - data = hashMapOf( - "qq" to ContactHelper.getUinByUidAsync(text.atNtUid), - ) - ) - } else { - MessageSegment( - type = "text", - data = hashMapOf( - "text" to text.content - ) - ) - } - } - - /** - * 小表情 / 戳一戳 消息转换消息段 - */ - private suspend fun convertFaceElem( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val face = element.faceElement - - if (face.faceType == 5) { - return MessageSegment( - type = "poke", - data = hashMapOf( - "type" to face.pokeType, - "id" to face.vaspokeId, - "strength" to face.pokeStrength - ) - ) - } - when (face.faceIndex) { - 114 -> { - return MessageSegment( - type = "basketball", - data = hashMapOf( - "id" to face.resultId.ifEmpty { "0" }.toInt(), - ) - ) - } - - 358 -> { - if (face.sourceType == 1) return MessageSegment("new_dice") - return MessageSegment( - type = "new_dice", - data = hashMapOf( - "id" to face.resultId.ifEmpty { "0" }.toInt() - ) - ) - } - - 359 -> { - if (face.resultId.isEmpty()) return MessageSegment("new_rps") - return MessageSegment( - type = "new_rps", - data = hashMapOf( - "id" to face.resultId.ifEmpty { "0" }.toInt() - ) - ) - } - - 394 -> { - //LogCenter.log(face.toString()) - return MessageSegment( - type = "face", - data = hashMapOf( - "id" to face.faceIndex, - "big" to (face.faceType == 3), - "result" to (face.resultId ?: "1") - ) - ) - } - - else -> return MessageSegment( - type = "face", - data = hashMapOf( - "id" to face.faceIndex, - "big" to (face.faceType == 3) - ) - ) - } - } - - /** - * 图片消息转换消息段 - */ - private suspend fun convertImageElem( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - 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 = chatType, - size = image.fileSize, - sha = "", - fileId = image.fileUuid, - storeId = storeId, - ) - ) - - //LogCenter.log(image.toString()) - - val originalUrl = image.originImageUrl ?: "" - LogCenter.log({ "receive image: $image" }, Level.DEBUG) - - /* - PicElement{picSubType=0,fileName=A655FCDADABC40D0CEAF6F9AF92937CD.jpg,fileSize=142865,picWidth=886,picHeight=1920,original=false,md5HexStr=a655fcdadabc40d0ceaf6f9af92937cd,sourcePath=null,thumbPath=null,transferStatus=2,progress=0,picType=1000,invalidState=0,fileUuid=CgoxMDI5Mzc0MTE1EhTnucgrUbp3MJjjagUM2-VxSQ5V7hiR3Agg_goo9ZCZt-HNhANQgJqeAQ,fileSubId=,thumbFileSize=0,fileBizId=null,downloadIndex=null,summary=,emojiFrom=null,emojiWebUrl=null,emojiAd=EmojiAD{url=,desc=,},emojiMall=EmojiMall{packageId=0,emojiId=0,},emojiZplan=EmojiZPlan{actionId=0,actionName=,actionType=0,playerNumber=0,peerUid=0,bytesReserveInfo=,},originImageMd5=,originImageUrl=null,importRichMediaContext=null,isFlashPic=false,} - */ - return MessageSegment( - type = "image", - data = hashMapOf( - "file" to md5, - "url" to when (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 = peerId - ) - - 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 = peerId, - 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 = peerId, - subPeer = subPeer - ) - - else -> throw UnsupportedOperationException("Not supported chat type: $chatType") - }, - "subType" to image.picSubType, - "type" to if (image.isFlashPic == true) "flash" else if (image.original) "original" else "show" - ) - ) - } - - /** - * 语音消息转换消息段 - */ - private suspend fun convertVoiceElem( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val record = element.pttElement - - val md5 = if (record.fileName.startsWith("silk")) - record.fileName.substring(5) - else record.md5HexStr - - return MessageSegment( - type = "record", - data = hashMapOf( - "file" to md5, - "url" to when (chatType) { - MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPttDownUrl("0", record.fileUuid) - MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupPttDownUrl( - "0", - md5.hex2ByteArray(), - record.fileUuid - ) - - else -> throw UnsupportedOperationException("Not supported chat type: $chatType") - } - ).also { - if (record.voiceChangeType != MsgConstant.KPTTVOICECHANGETYPENONE) { - it["magic"] = "1" - } - if ((it["url"] as String).isBlank()) { - it.remove("url") - } - } - ) - } - - /** - * 视频消息转换消息段 - */ - private suspend fun convertVideoElem( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val video = element.videoElement - 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() - - //LogCenter.log({ "receive video msg: $video" }, Level.DEBUG) - - return MessageSegment( - type = "video", - data = hashMapOf( - "file" to video.fileName, - "url" to when (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: $chatType") - } - ).also { - if ((it["url"] as String).isBlank()) - it.remove("url") - } - ) - } - - /** - * 商城大表情消息转换消息段 - */ - private suspend fun convertMarketFaceElem( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val face = element.marketFaceElement - return when (face.emojiId.lowercase()) { - "4823d3adb15df08014ce5d6796b76ee1" -> MessageSegment("dice") - "83c8a293ae65ca140f348120a77448ee" -> MessageSegment("rps") - else -> MessageSegment( - type = "mface", - data = hashMapOf( - "id" to face.emojiId - ) - ) - } - } - - /** - * JSON消息转消息段 - */ - private suspend fun convertStructJsonElem( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val data = element.arkElement.bytesData.asJsonObject - return when (data["app"].asString) { - "com.tencent.multimsg" -> { - val info = data["meta"].asJsonObject["detail"].asJsonObject - MessageSegment( - type = "forward", - data = mapOf( - "id" to info["resid"].asString, - "filename" to info["uniseq"].asString, - "summary" to info["summary"].asString, - "desc" to info["news"].asJsonArray.joinToString("\n") { it.asJsonObject["text"].asString } - ) - ) - } - - "com.tencent.troopsharecard" -> { - val info = data["meta"].asJsonObject["contact"].asJsonObject - MessageSegment( - type = "contact", - data = hashMapOf( - "type" to "group", - "id" to info["jumpUrl"].asString.split("group_code=")[1] - ) - ) - } - - "com.tencent.contact.lua" -> { - val info = data["meta"].asJsonObject["contact"].asJsonObject - MessageSegment( - type = "contact", - data = hashMapOf( - "type" to "private", - "id" to info["jumpUrl"].asString.split("uin=")[1] - ) - ) - } - - "com.tencent.map" -> { - val info = data["meta"].asJsonObject["Location.Search"].asJsonObject - MessageSegment( - type = "location", - data = hashMapOf( - "lat" to info["lat"].asString, - "lon" to info["lng"].asString, - "content" to info["address"].asString, - "title" to info["name"].asString - ) - ) - } - - else -> MessageSegment( - type = "json", - data = mapOf( - "data" to element.arkElement.bytesData.asJsonObject.toString() - ) - ) - } - } - - /** - * 回复消息转消息段 - */ - private suspend fun convertReplyElem( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val reply = element.replyElement - val msgId = reply.replayMsgId - val msgHash = if (msgId != 0L) { - MessageHelper.generateMsgIdHash(chatType, msgId) - } else { - MessageDB.getInstance().messageMappingDao() - .queryByMsgSeq(chatType, peerId, reply.replayMsgSeq?.toInt() ?: 0)?.msgHashId - ?: kotlin.run { - LogCenter.log("消息映射关系未找到: Message($reply)", Level.WARN) - MessageHelper.generateMsgIdHash(chatType, reply.sourceMsgIdInRecords) - } - } - - return MessageSegment( - type = "reply", - data = mapOf( - "id" to msgHash - ) - ) - } - - /** - * 灰色提示条消息过滤 - */ - private suspend fun convertGrayTipsElem( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val tip = element.grayTipElement - when (tip.subElementType) { - MsgConstant.GRAYTIPELEMENTSUBTYPEJSON -> { - val notify = tip.jsonGrayTipElement - when (notify.busiId) { - /* 新人入群 */ 17L, /* 群戳一戳 */1061L, - /* 群撤回 */1014L, /* 群设精消息 */2401L, - /* 群头衔 */2407L -> { - } - - else -> LogCenter.log("不支持的灰条类型(JSON): ${notify.busiId}", Level.WARN) - } - } - - MsgConstant.GRAYTIPELEMENTSUBTYPEXMLMSG -> { - val notify = tip.xmlElement - when (notify.busiId) { - /* 群戳一戳 */1061L, /* 群打卡 */1068L -> {} - else -> LogCenter.log("不支持的灰条类型(XML): ${notify.busiId}", Level.WARN) - } - } - - else -> LogCenter.log("不支持的提示类型: ${tip.subElementType}", Level.WARN) - } - // 提示类消息,这里提供的是一个xml,不具备解析通用性 - // 在这里不推送 - throw UnknownError() - } - - /** - * 文件消息转换消息段 - */ - private suspend fun convertFileElem( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - 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 (chatType) { - MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId) - MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildFileDownUrl(peerId, subPeer, fileId, bizId) - else -> RichProtoSvc.getGroupFileDownUrl(peerId.toLong(), fileId, bizId) - } - - return MessageSegment( - type = "file", - data = mapOf( - "name" to fileName, - "size" to fileSize, - "expire" to expireTime, - "id" to fileId, - "url" to url, - "biz" to bizId, - "sub" to fileSubId - ) - ) - } - - /** - * 老板QQ的合并转发信息 - */ - private suspend fun convertXmlMultiMsgElem( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val multiMsg = element.multiForwardMsgElement - return MessageSegment( - type = "forward", - data = mapOf( - "id" to multiMsg.resId - ) - ) - } - - private suspend fun convertXmlLongMsgElem( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val longMsg = element.structLongMsgElement - return MessageSegment( - type = "forward", - data = mapOf( - "id" to longMsg.resId - ) - ) - } - - private suspend fun convertMarkdownElem( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val markdown = element.markdownElement - return MessageSegment( - type = "markdown", - data = mapOf( - "content" to markdown.content - ) - ) - } - - private suspend fun convertBubbleFaceElem( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val bubbleElement = element.faceBubbleElement - return MessageSegment( - type = "bubble_face", - data = mapOf( - "id" to bubbleElement.yellowFaceInfo.index, - "count" to (bubbleElement.faceCount ?: 1), - ) - ) - } - - private suspend fun convertInlineKeyboardElem( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val keyboard = element.inlineKeyboardElement - return MessageSegment( - type = "button", - data = mapOf( - "rows" to keyboard.rows.map { row -> - mapOf("buttons" to row.buttons.map { button -> - mapOf( - "id" to button.id, - "render_data" to mapOf( - "label" to (button?.label ?: ""), - "visited_label" to (button?.visitedLabel ?: ""), - "style" to (button?.style ?: 0) - - ), - "action" to mapOf( - "type" to (button?.type ?: 0), - "permission" to mapOf( - "type" to (button?.permissionType ?: 0), - "specify_role_ids" to button?.specifyRoleIds, - "specify_user_ids" to button?.specifyTinyids - ), - "unsupport_tips" to (button?.unsupportTips ?: ""), - "data" to (button?.data ?: ""), - "reply" to button?.isReply, - "enter" to button?.enter - ) - ) - }) - - }, - "appid" to keyboard.botAppid - ) - ) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/maker/ElemMaker.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/maker/ElemMaker.kt deleted file mode 100644 index 58f15e51..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/maker/ElemMaker.kt +++ /dev/null @@ -1,718 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet.msg.maker - -import android.graphics.BitmapFactory -import androidx.exifinterface.media.ExifInterface -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import kotlinx.serialization.json.JsonObject -import moe.fuqiuluo.qqinterface.servlet.CardSvc -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.qqinterface.servlet.MsgSvc -import moe.fuqiuluo.qqinterface.servlet.TicketSvc -import moe.fuqiuluo.qqinterface.servlet.ark.WeatherSvc -import moe.fuqiuluo.qqinterface.servlet.msg.toJson -import moe.fuqiuluo.qqinterface.servlet.msg.toSegments -import moe.fuqiuluo.qqinterface.servlet.transfile.NtV2RichMediaSvc -import moe.fuqiuluo.qqinterface.servlet.transfile.NtV2RichMediaSvc.fetchGroupResUploadTo -import moe.fuqiuluo.shamrock.helper.* -import moe.fuqiuluo.shamrock.helper.MessageHelper.messageArrayToRichText -import moe.fuqiuluo.shamrock.helper.MessageHelper.obtainMessageTypeByDetailType -import moe.fuqiuluo.shamrock.tools.* -import moe.fuqiuluo.shamrock.utils.DeflateTools -import moe.fuqiuluo.shamrock.utils.FileUtils -import protobuf.auto.toByteArray -import protobuf.message.Elem -import protobuf.message.Ptt -import protobuf.message.RichText -import protobuf.message.element.* -import protobuf.message.element.commelem.* -import protobuf.oidb.cmd0x11c5.C2CUserInfo -import protobuf.oidb.cmd0x11c5.GroupUserInfo -import java.io.File -import java.nio.ByteBuffer -import java.util.* -import kotlin.random.Random -import kotlin.random.nextULong -import kotlin.time.Duration.Companion.seconds - -internal typealias IElemMaker = suspend (ElemMaker, Int, Long, String, JsonObject) -> Unit - -internal class ElemMaker { - companion object { - private val makerArray = hashMapOf( - "text" to ElemMaker::createTextElem, - "at" to ElemMaker::createAtElem, - "face" to ElemMaker::createFaceElem, - "pic" to ElemMaker::createImageElem, - "image" to ElemMaker::createImageElem, - "reply" to ElemMaker::createReplyElem, - "forward" to ElemMaker::createForwardStruct, - "weather" to ElemMaker::createWeatherElem, - "json" to ElemMaker::createJsonElem, - "poke" to ElemMaker::createPokeElem, - "dice" to ElemMaker::createNewDiceElem, - "rps" to ElemMaker::createNewRpsElem, - "markdown" to ElemMaker::createMarkdownElem, - "button" to ElemMaker::createButtonElem, -// "anonymous" to ElemMaker::createAnonymousElem, -// "share" to ElemMaker::createShareElem, -// "contact" to ElemMaker::createContactElem, -// "location" to ElemMaker::createLocationElem, -// "music" to ElemMaker::createMusicElem, -// "touch" to ElemMaker::createTouchElem, -// "multi_msg" to MessageMaker::createLongMsgStruct, -// "bubble_face" to ElemMaker::createBubbleFaceElem, - "voice" to ElemMaker::createRecordElem, - "record" to ElemMaker::createRecordElem, -// "video" to ElemMaker::createVideoElem, - ) - - operator fun get(type: String): IElemMaker? = makerArray[type] - } - - private var rich = RichText() - private val elems = mutableListOf() - private var summary = StringBuilder() - - fun getRich(): RichText { - rich.elements = elems - return rich - } - - fun getDesc(): String { - return summary.toString() - } - - private suspend fun createTextElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ) { - data.checkAndThrow("text") - val text = data["text"].asString - val elem = Elem( - text = TextMsg(text) - ) - elems.add(elem) - summary.append(text) - } - - private suspend fun createAtElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ) { - when (chatType) { - MsgConstant.KCHATTYPEGROUP -> { - data.checkAndThrow("qq") - val qq: Long - val type: Int - val display = when (val qqStr = data["qq"].asString) { - "0", "all" -> { - qq = 0 - type = 1 - "@全体成员" - } - - "online" -> { - qq = 0 - type = 64 - "@在线成员" - } - - else -> { - qq = qqStr.toLong() - type = 0 - "@" + (data["name"].asStringOrNull ?: GroupSvc.getTroopMemberInfoByUinV2( - peerId.toLong(), - qq, - true - ).let { - val info = it.getOrNull() - if (info == null) - LogCenter.log("无法获取群成员信息: $qqStr", Level.ERROR) - else info.troopnick - .ifNullOrEmpty(info.friendnick) - .ifNullOrEmpty(qqStr) - }) - } - } - - val attr6: ByteBuffer = ByteBuffer.allocate(6) - attr6.put(byteArrayOf(0, 1, 0, 0, 0)) - attr6.putChar(display.length.toChar()) - attr6.putChar(type.toChar()) - attr6.putBuf32Long(qq) - attr6.put(byteArrayOf(0, 0)) - val elem = Elem( - text = TextMsg(str = display, attr6Buf = attr6.array()) - ) - elems.add(elem) - summary.append(display) - } - - MsgConstant.KCHATTYPEC2C -> { - data.checkAndThrow("qq") - - val qq = data["qq"].asLong - val display = - "@" + (data["name"].asStringOrNull ?: CardSvc.getProfileCard(qq) - .onSuccess { - it.strNick.ifNullOrEmpty(qq.toString()) - }.onFailure { - LogCenter.log("无法获取QQ信息: $qq", Level.WARN) - }) - - val elem = Elem( - text = TextMsg(str = display) - ) - elems.add(elem) - summary.append(display) - } - - else -> throw UnsupportedOperationException("Unsupported chatType($chatType) for AtMsg") - } - } - - private suspend fun createFaceElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ) { - data.checkAndThrow("id") - val faceId = data["id"].asInt - val elem = if (data["big"].asBooleanOrNull == true) { - Elem( - commonElem = CommonElem( - serviceType = 37, - elem = QFaceExtra( - packId = "1", - stickerId = "1", - faceId = faceId, - field4 = 1, - field5 = 1, - result = "", - faceText = "", //todo 表情名字 - field9 = 1 - ).toByteArray(), - businessType = 1 - ) - ) - } else { - Elem( - face = FaceMsg( - index = faceId - ) - ) - } - elems.add(elem) - summary.append("[表情]") - } - - private suspend fun createImageElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ) { - val type = data["type"].asStringOrNull ?: "original" - val isOriginal = type == "original" - val filePath = data["file"].asStringOrNull - val url = data["url"].asStringOrNull - var file: File? = null - if (filePath != null) { - val md5 = filePath - .replace(regex = "[{}\\-]".toRegex(), replacement = "") - .split(".")[0].lowercase() - file = if (md5.length == 32) { - FileUtils.getFileByMd5(md5) - } else { - FileUtils.parseAndSave(filePath) - } - } - if ((file == null || !file.exists()) && url != null) { - file = FileUtils.parseAndSave(url) - } - if (file?.exists() == false) { - throw LogicException("Image(${file.name}) file is not exists, please check your filename.") - } - requireNotNull(file) - - 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 - ) - val picWidth: Int - val picHeight: Int - if (orientation != ExifInterface.ORIENTATION_ROTATE_90 && orientation != ExifInterface.ORIENTATION_ROTATE_270) { - picWidth = options.outWidth - picHeight = options.outHeight - } else { - picWidth = options.outHeight - picHeight = options.outWidth - } - - val fileInfo = NtV2RichMediaSvc.tryUploadResourceByNt( - chatType = chatType, - elementType = MsgConstant.KELEMTYPEPIC, - resources = arrayListOf(file), - timeout = 30.seconds - ).getOrThrow().first() - - runCatching { - fileInfo.uuid.toUInt() - }.onFailure { - NtV2RichMediaSvc.requestUploadNtPic(file, fileInfo.md5, fileInfo.sha, fileInfo.fileName, picWidth.toUInt(), picHeight.toUInt(), 5, chatType) { - when(chatType) { - MsgConstant.KCHATTYPEGROUP -> { - sceneType = 2u - grp = GroupUserInfo(fetchGroupResUploadTo().toULong()) - } - MsgConstant.KCHATTYPEC2C -> { - sceneType = 1u - c2c = C2CUserInfo( - accountType = 2u, - uid = ContactHelper.getUidByUinAsync(peerId.toLong()) - ) - } - else -> error("不支持的合并转发图片类型") - } - }.onFailure { - LogCenter.log("获取MultiMedia图片信息失败: $it", Level.ERROR) - }.onSuccess { - //LogCenter.log({ "获取MultiMedia图片信息成功: ${it.hashCode()}" }, Level.INFO) - elems.add(Elem( - commonElem = CommonElem( - serviceType = 48, - businessType = 10, - elem = it.msgInfo!!.toByteArray() - ) - )) - } - }.onSuccess { uuid -> - elems.add(when (chatType) { - MsgConstant.KCHATTYPEGROUP -> Elem( - customFace = CustomFace( - filePath = fileInfo.fileName, - fileId = uuid, - serverIp = 0u, - serverPort = 0u, - fileType = FileUtils.getPicType(file).toUInt(), - useful = 1u, - md5 = fileInfo.md5.hex2ByteArray(), - bizType = data["subType"].asIntOrNull?.toUInt(), - imageType = FileUtils.getPicType(file).toUInt(), - width = picWidth.toUInt(), - height = picHeight.toUInt(), - size = fileInfo.fileSize.toUInt(), - origin = isOriginal, - thumbWidth = 0u, - thumbHeight = 0u, - pbReserve = CustomFace.Companion.PbReserve( - field1 = 0, - field3 = 0, - field4 = 0, - field10 = 0, - field21 = CustomFace.Companion.Object1( - field1 = 0, - field2 = "", - field3 = 0, - field4 = 0, - field5 = 0, - md5Str = fileInfo.md5 - ) - ) - ) - ) - MsgConstant.KCHATTYPEC2C -> Elem( - notOnlineImage = NotOnlineImage( - filePath = fileInfo.fileName, - fileLen = fileInfo.fileSize.toUInt(), - downloadPath = fileInfo.uuid, - imgType = FileUtils.getPicType(file).toUInt(), - picMd5 = fileInfo.md5.hex2ByteArray(), - picHeight = picWidth.toUInt(), - picWidth = picHeight.toUInt(), - resId = fileInfo.uuid, - original = isOriginal, // true - pbReserve = NotOnlineImage.Companion.PbReserve( - field1 = 0, - field3 = 0, - field4 = 0, - field10 = 0, - field20 = NotOnlineImage.Companion.Object1( - field1 = 0, - field2 = "", - field3 = 0, - field4 = 0, - field5 = 0, - field7 = "", - ), - md5Str = fileInfo.md5 - ) - ) - ) - else -> throw LogicException("Not supported chatType($chatType) for PictureMsg") - }) - } - - summary.append("[图片]") - } - - private suspend fun createReplyElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ) { - data.checkAndThrow("id") - val msgHash = data["id"].asInt - val mapping = MessageHelper.getMsgMappingByHash(msgHash) - ?: throw Exception("不存在该消息映射,无法回复消息") - - if (mapping.qqMsgId == 0L) { - // 貌似获取失败了,555 - throw Exception("无法获取被回复消息") - } - - val elem = if (data.containsKey("text")) { - data.checkAndThrow("qq", "time", "seq") - Elem( - srcMsg = SourceMsg( - origSeqs = listOf(data["seq"].asInt), - senderUin = data["qq"].asString.toULong(), - time = data["time"].asString.toULong(), - flag = 1u, - elems = listOf( - Elem( - text = TextMsg( - data["text"].asString - ) - ) - ), - type = 0u, - pbReserve = SourceMsg.Companion.PbReserve( - msgRand = Random.nextInt().toULong(), - field8 = Random.nextInt(0, 10000) - ), - ) - ) - } else { - val msg = - MsgSvc.getMsgByQMsgId(chatType, mapping.peerId, mapping.qqMsgId).getOrNull() - ?: throw Exception("无法获取被回复消息") - Elem( - srcMsg = SourceMsg( - origSeqs = listOf(msg.msgSeq.toInt()), - senderUin = msg.senderUin.toULong(), - time = msg.msgTime.toULong(), - flag = 1u, - elems = messageArrayToRichText( - msg.chatType, - msg.msgId, - msg.peerUin.toString(), - msg.elements.toSegments( - msg.chatType, - if (msg.chatType == MsgConstant.KCHATTYPEGUILD) msg.guildId else msg.peerUin.toString(), - msg.channelId ?: msg.peerUin.toString() - ).toJson() - ).getOrElse { throw Exception("解析回复消息失败: $it") }.second.elements, - type = 0u, - pbReserve = SourceMsg.Companion.PbReserve( - msgRand = Random.nextULong(), - senderUid = msg.senderUid, - receiverUid = TicketSvc.getUid(), - field8 = Random.nextInt(0, 10000) - ), - ) - ) - } - elems.add(elem) - summary.append("[回复消息]") - } - - private suspend fun createJsonElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ) { - data.checkAndThrow("data") - - val elem = Elem( - lightApp = LightAppElem( - data = byteArrayOf(1) + DeflateTools.compress(data.toString().toByteArray()) - ) - ) - elems.add(elem) - summary .append( "[Json消息]" ) - } - - private suspend fun createForwardStruct( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ) { - data.checkAndThrow("id") - val resId = data["id"].asString - val filename = data["filename"].asStringOrNull ?: UUID.randomUUID().toString().uppercase() - var summary = data["summary"].asStringOrNull - val descriptions = data["desc"].asStringOrNull - var news = descriptions?.split("\n")?.map { "text" to it } - - if (news == null || summary == null) { - val forwardMsg = MsgSvc.getForwardMsg(resId).getOrThrow() - if (news == null) { - news = forwardMsg.map { - "text" to it.sender.nickName + ": " + messageArrayToRichText( - obtainMessageTypeByDetailType(it.msgType), - it.qqMsgId, - it.peerId.toString(), - it.message.json - ).getOrThrow().first - } - } - if (summary == null) { - summary = "查看${forwardMsg.size}条转发消息" - } - } - - val json = mapOf( - "app" to "com.tencent.multimsg", - "config" to mapOf( - "autosize" to 1, - "forward" to 1, - "round" to 1, - "type" to "normal", - "width" to 300 - ), - "desc" to "[聊天记录]", - "extra" to mapOf( - "filename" to filename, - "tsum" to 2 - ).json.toString(), - "meta" to mapOf( - "detail" to mapOf( - "news" to news, - "resid" to resId, - "source" to "群聊的聊天记录", - "summary" to summary, - "uniseq" to filename - ) - ), - "prompt" to "[聊天记录]", - "ver" to "0.0.0.5", - "view" to "contact" - ) - val elem = Elem( - lightApp = LightAppElem( - data = byteArrayOf(1) + DeflateTools.compress(json.json.toString().toByteArray()) - ) - ) - elems.add(elem) - this.summary .append( "[聊天记录]" ) - } - - private suspend fun createWeatherElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ) { - var code = data["code"].asIntOrNull - - if (code == null) { - data.checkAndThrow("city") - val city = data["city"].asString - code = WeatherSvc.searchCity(city).onFailure { - LogCenter.log("无法获取城市天气: $city", Level.ERROR) - }.getOrNull()?.firstOrNull()?.adcode - } - - if (code != null) { - val weatherCard = WeatherSvc.fetchWeatherCard(code).getOrThrow() -// OidbSvc.0xdc2_34 -// 00 00 00 DF 08 C2 1B 10 22 22 C4 01 0A B7 01 08 A2 E0 F2 2F 10 01 18 00 2A 02 08 01 58 FB 91 F6 AE 02 62 A1 01 08 01 52 08 E5 8C 97 E4 BA AC 20 20 5A 19 2D 33 C2 B0 2F 33 C2 B0 0A E7 A9 BA E6 B0 94 E8 B4 A8 E9 87 8F 3A E8 89 AF 62 11 5B E5 88 86 E4 BA AB 5D 20 E5 8C 97 E4 BA AC 20 20 6A 25 68 74 74 70 73 3A 2F 2F 77 65 61 74 68 65 72 2E 6D 70 2E 71 71 2E 63 6F 6D 2F 3F 73 74 3D 30 26 5F 77 76 3D 31 72 3E 68 74 74 70 73 3A 2F 2F 69 6D 67 63 61 63 68 65 2E 71 71 2E 63 6F 6D 2F 61 63 2F 71 71 77 65 61 74 68 65 72 2F 69 6D 61 67 65 2F 73 68 61 72 65 5F 69 63 6F 6E 2F 66 69 6E 65 2E 70 6E 67 12 08 08 01 10 FB 91 F6 AE 02 32 0D 61 6E 64 72 6F 69 64 20 39 2E 30 2E 38 - val elem = Elem( - lightApp = LightAppElem( - data = byteArrayOf(1) + DeflateTools.compress( - weatherCard["weekStore"] - .asJsonObject["share"].asString.toByteArray() - ) - ) - ) - elems.add(elem) - summary .append( "[天气卡片]" ) - } else { - throw LogicException("无法获取城市天气") - } - } - - private suspend fun createPokeElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ) { - data.checkAndThrow("type", "id") - val elem = Elem( - commonElem = CommonElem( - serviceType = 2, - elem = PokeExtra( - type = data["type"].asInt, - field7 = 0, - field8 = 0 - ).toByteArray(), - businessType = data["id"].asInt - ) - ) - elems.add(elem) - summary .append( "[戳一戳]" ) - } - - private suspend fun createNewDiceElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ) { - val elem = Elem( - commonElem = CommonElem( - serviceType = 37, - elem = QFaceExtra( - packId = "1", - stickerId = "33", - faceId = 358, - field4 = 1, - field5 = 2, - result = "", - faceText = "/骰子", - field9 = 1 - ).toByteArray(), - businessType = 2 - ) - ) - elems.add(elem) - summary .append( "[骰子]" ) - } - - private suspend fun createNewRpsElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ) { - val elem = Elem( - commonElem = CommonElem( - serviceType = 37, - elem = QFaceExtra( - packId = "1", - stickerId = "34", - faceId = 359, - field4 = 1, - field5 = 2, - result = "", - faceText = "/包剪锤", - field9 = 1 - ).toByteArray(), - businessType = 1 - ) - ) - elems.add(elem) - summary .append( "[包剪锤]" ) - } - - private suspend fun createMarkdownElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ) { - data.checkAndThrow("content") - val elem = Elem( - commonElem = CommonElem( - serviceType = 45, - elem = MarkdownExtra(data["content"].asString).toByteArray(), - businessType = 1 - ) - ) - elems.add(elem) - summary.append("[Markdown消息]") - } - - private suspend fun createButtonElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ) { - data.checkAndThrow("rows") - val elem = Elem( - commonElem = CommonElem( - serviceType = 46, - elem = ButtonExtra( - field1 = Object1( - rows = data["rows"].asJsonArray.map { row -> - Row(buttons = row.asJsonObject["buttons"].asJsonArray.map { - val button = it.asJsonObject - val renderData = button["render_data"].asJsonObject - val action = button["action"].asJsonObject - val permission = action["permission"].asJsonObject - Button( - id = button["id"].asStringOrNull, - renderData = RenderData( - label = renderData["label"].asString, - visitedLabel = renderData["visited_label"].asString, - style = renderData["style"].asInt - ), - action = Action( - type = action["type"].asInt, - permission = Permission( - type = permission["type"].asInt, - specifyRoleIds = permission["specify_role_ids"].asJsonArrayOrNull?.map { id -> id.asString }, - specifyUserIds = permission["specify_user_ids"].asJsonArrayOrNull?.map { id -> id.asString } - ), - unsupportTips = action["unsupport_tips"].asString, - data = action["data"].asString, - reply = action["reply"].asBooleanOrNull, - enter = action["enter"].asBooleanOrNull - ) - ) - }) - }, - appid = data["appid"].asIntOrNull - ) - ).toByteArray(), - businessType = 1 - ) - ) - elems.add(elem) - summary.append("[Button消息]") - } - - private suspend fun createRecordElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ) { - data.checkAndThrow("content") - rich.ptt= Ptt( - - ) - summary .append( "[语音消息]" ) - } - - private fun JsonObject.checkAndThrow(vararg key: String) { - key.forEach { - if (!containsKey(it)) throw ParamsException(it) - } - } -} diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/maker/NtMsgElementMaker.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/maker/NtMsgElementMaker.kt deleted file mode 100644 index f064e764..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/maker/NtMsgElementMaker.kt +++ /dev/null @@ -1,1103 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet.msg.maker - -import android.graphics.BitmapFactory -import androidx.exifinterface.media.ExifInterface -import com.tencent.mobileqq.app.QQAppInterface -import com.tencent.mobileqq.emoticon.QQSysFaceUtil -import com.tencent.mobileqq.pb.ByteStringMicro -import com.tencent.mobileqq.qroute.QRoute -import com.tencent.qphone.base.remote.ToServiceMsg -import com.tencent.qqnt.aio.adapter.api.IAIOPttApi -import com.tencent.qqnt.kernel.nativeinterface.* -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import moe.fuqiuluo.qqinterface.servlet.CardSvc -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.qqinterface.servlet.LbsSvc -import moe.fuqiuluo.qqinterface.servlet.MsgSvc -import moe.fuqiuluo.qqinterface.servlet.ark.data.ArkAppInfo -import moe.fuqiuluo.qqinterface.servlet.ark.ArkMsgSvc -import moe.fuqiuluo.qqinterface.servlet.ark.WeatherSvc -import moe.fuqiuluo.qqinterface.servlet.transfile.* -import moe.fuqiuluo.qqinterface.servlet.transfile.FileTransfer -import moe.fuqiuluo.qqinterface.servlet.transfile.data.PictureResource -import moe.fuqiuluo.qqinterface.servlet.transfile.data.Private -import moe.fuqiuluo.qqinterface.servlet.transfile.Transfer -import moe.fuqiuluo.qqinterface.servlet.transfile.data.Troop -import moe.fuqiuluo.qqinterface.servlet.transfile.data.VideoResource -import moe.fuqiuluo.qqinterface.servlet.transfile.data.VoiceResource -import moe.fuqiuluo.shamrock.helper.ActionMsgException -import moe.fuqiuluo.shamrock.helper.ContactHelper -import moe.fuqiuluo.shamrock.helper.IllegalParamsException -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LocalCacheHelper -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.helper.LogicException -import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.helper.MusicHelper -import moe.fuqiuluo.shamrock.helper.ParamsException -import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig -import moe.fuqiuluo.shamrock.tools.* -import moe.fuqiuluo.shamrock.utils.AudioUtils -import moe.fuqiuluo.shamrock.utils.FileUtils -import moe.fuqiuluo.shamrock.utils.MediaType -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher -import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher -import moe.fuqiuluo.shamrock.xposed.helper.msgService -import mqq.app.MobileQQ -import tencent.im.oidb.cmd0xb77.oidb_cmd0xb77 -import tencent.im.oidb.cmd0xdc2.oidb_cmd0xdc2 -import tencent.im.oidb.oidb_sso -import java.io.File -import java.util.* -import kotlin.collections.ArrayList -import kotlin.math.roundToInt -import kotlin.random.Random -import kotlin.random.nextInt - -internal typealias IMsgElementMaker = suspend (Int, Long, String, JsonObject) -> Result - -internal object NtMsgElementMaker { - private val makerMap = hashMapOf( - "text" to NtMsgElementMaker::createTextElem, - "face" to NtMsgElementMaker::createFaceElem, - "pic" to NtMsgElementMaker::createImageElem, - "image" to NtMsgElementMaker::createImageElem, - "voice" to NtMsgElementMaker::createRecordElem, - "record" to NtMsgElementMaker::createRecordElem, - "at" to NtMsgElementMaker::createAtElem, - "video" to NtMsgElementMaker::createVideoElem, - "markdown" to NtMsgElementMaker::createMarkdownElem, - "dice" to NtMsgElementMaker::createDiceElem, - "rps" to NtMsgElementMaker::createRpsElem, - "poke" to NtMsgElementMaker::createPokeElem, - "anonymous" to NtMsgElementMaker::createAnonymousElem, - "share" to NtMsgElementMaker::createShareElem, - "contact" to NtMsgElementMaker::createContactElem, - "location" to NtMsgElementMaker::createLocationElem, - "music" to NtMsgElementMaker::createMusicElem, - "reply" to NtMsgElementMaker::createReplyElem, - "touch" to NtMsgElementMaker::createTouchElem, - "weather" to NtMsgElementMaker::createWeatherElem, - "json" to NtMsgElementMaker::createJsonElem, - "forward" to NtMsgElementMaker::createForwardStruct, - "new_dice" to NtMsgElementMaker::createNewDiceElem, - "new_rps" to NtMsgElementMaker::createNewRpsElem, - "basketball" to NtMsgElementMaker::createBasketballElem, - //"multi_msg" to NtMsgElementMaker::createLongMsgStruct, - "bubble_face" to NtMsgElementMaker::createBubbleFaceElem, - "button" to NtMsgElementMaker::createInlineKeywordElem, - "inline_keyboard" to NtMsgElementMaker::createInlineKeywordElem - ) - - operator fun get(type: String): IMsgElementMaker? = makerMap[type] - - private suspend fun createForwardStruct( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ): Result { - data.checkAndThrow("id") - val resId = data["id"].asString - val filename = data["filename"].asStringOrNull ?: UUID.randomUUID().toString().uppercase() - var summary = data["summary"].asStringOrNull - val descriptions = data["desc"].asStringOrNull - var news = descriptions?.split("\n")?.map { "text" to it } - - if (news == null || summary == null) { - val forwardMsg = MsgSvc.getForwardMsg(resId).getOrElse { return Result.failure(it) } - if (news == null) { - news = forwardMsg.map { - "text" to it.sender.nickName + ": " + MessageHelper.messageArrayToRichText( - MessageHelper.obtainMessageTypeByDetailType(it.msgType), - it.qqMsgId, - it.peerId.toString(), - it.message.json - ).getOrThrow().first - } - } - if (summary == null) { - summary = "查看${forwardMsg.size}条转发消息" - } - } - - val json = mapOf( - "app" to "com.tencent.multimsg", - "config" to mapOf( - "autosize" to 1, - "forward" to 1, - "round" to 1, - "type" to "normal", - "width" to 300 - ), - "desc" to "[聊天记录]", - "extra" to mapOf( - "filename" to filename, - "tsum" to 2 - ).json.toString(), - "meta" to mapOf( - "detail" to mapOf( - "news" to news, - "resid" to resId, - "source" to "群聊的聊天记录", - "summary" to summary, - "uniseq" to filename - ) - ), - "prompt" to "[聊天记录]", - "ver" to "0.0.0.5", - "view" to "contact" - ) - return createJsonElem( - chatType, msgId, peerId, mapOf( - "data" to json - ).json - ) - } - - private suspend fun createInlineKeywordElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ): Result { - fun tryNewKeyboardButton(button: JsonObject): InlineKeyboardButton { - val renderData = button["render_data"].asJsonObject - val action = button["action"].asJsonObject - val permission = action["permission"].asJsonObject - return runCatching { - InlineKeyboardButton( - button["id"].asStringOrNull ?: "", - renderData["label"].asString, - renderData["visited_label"].asString, - renderData["style"].asInt, - action["type"].asInt, - action["click_limit"].asInt, - action["unsupport_tips"].asString, - action["data"].asString, - action["at_bot_show_channel_list"].asBooleanOrNull ?: false, - permission["type"].asInt, - ArrayList(permission["specify_role_ids"].asJsonArrayOrNull?.map { id -> id.asString } - ?: arrayListOf()), - ArrayList(permission["specify_user_ids"].asJsonArrayOrNull?.map { id -> id.asString } - ?: arrayListOf()), - false, 0, false, arrayListOf() - ) - }.getOrElse { - InlineKeyboardButton( - button["id"].asStringOrNull ?: "", - renderData["label"].asString, - renderData["visited_label"].asString, - renderData["style"].asInt, - action["type"].asInt, - action["click_limit"].asInt, - action["unsupport_tips"].asString, - action["data"].asString, - action["at_bot_show_channel_list"].asBooleanOrNull ?: false, - permission["type"].asInt, - ArrayList(permission["specify_role_ids"].asJsonArrayOrNull?.map { id -> id.asString } - ?: arrayListOf()), - ArrayList(permission["specify_user_ids"].asJsonArrayOrNull?.map { id -> id.asString } - ?: arrayListOf()), - ) - } - } - - val elem = MsgElement() - elem.elementType = MsgConstant.KELEMTYPEINLINEKEYBOARD - val rows = arrayListOf() - - val keyboard = Json.parseToJsonElement(data["data"].asString).asJsonObject - keyboard["rows"].asJsonArray.forEach { - val row = it.asJsonObject - val buttons = arrayListOf() - row["buttons"].asJsonArray.forEach { button -> - val btn = button.asJsonObject - buttons.add(tryNewKeyboardButton(btn)) - } - rows.add(InlineKeyboardRow(buttons)) - } - elem.inlineKeyboardElement = InlineKeyboardElement(rows, keyboard["appid"].asLong) - return Result.success(elem) - } - - private suspend fun createBubbleFaceElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ): Result { - data.checkAndThrow("id", "count") - val faceId = data["id"].asInt - val local = QQSysFaceUtil.convertToLocal(faceId) - val name = QQSysFaceUtil.getFaceDescription(local) - val count = data["count"].asInt - val elem = MsgElement() - elem.elementType = MsgConstant.KELEMTYPEFACEBUBBLE - val face = FaceBubbleElement() - face.faceType = 13 - face.faceCount = count - face.faceSummary = QQSysFaceUtil.getPrueFaceDescription(name) - val smallYellowFaceInfo = SmallYellowFaceInfo() - smallYellowFaceInfo.index = faceId - smallYellowFaceInfo.compatibleText = face.faceSummary - smallYellowFaceInfo.text = face.faceSummary - face.yellowFaceInfo = smallYellowFaceInfo - face.faceFlag = 0 - face.content = data["text"].asStringOrNull ?: "[${face.faceSummary}]x$count" - elem.faceBubbleElement = face - return Result.success(elem) - } - - private suspend fun createBasketballElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ): Result { - val elem = MsgElement() - elem.elementType = MsgConstant.KELEMTYPEFACE - val face = FaceElement() - face.faceIndex = 114 - face.faceText = "/篮球" - face.faceType = 3 - face.packId = "1" - face.stickerId = "13" - face.sourceType = 1 - face.stickerType = 2 - face.resultId = Random.nextInt(1..5).toString() - face.surpriseId = "" - face.randomType = 1 - elem.faceElement = face - return Result.success(elem) - } - - private suspend fun createNewRpsElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ): Result { - val elem = MsgElement() - elem.elementType = MsgConstant.KELEMTYPEFACE - val face = FaceElement() - face.faceIndex = 359 - face.faceText = "/包剪锤" - face.faceType = 3 - face.packId = "1" - face.stickerId = "34" - face.sourceType = 1 - face.stickerType = 2 - face.resultId = "" - face.surpriseId = "" - face.randomType = 1 - elem.faceElement = face - return Result.success(elem) - } - - private suspend fun createNewDiceElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ): Result { - val elem = MsgElement() - elem.elementType = MsgConstant.KELEMTYPEFACE - val face = FaceElement() - face.faceIndex = 358 - face.faceText = "/骰子" - face.faceType = 3 - face.packId = "1" - face.stickerId = "33" - face.sourceType = 1 - face.stickerType = 2 - face.resultId = "" - face.surpriseId = "" - face.randomType = 1 - elem.faceElement = face - return Result.success(elem) - } - - private suspend fun createJsonElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ): Result { - data.checkAndThrow("data") - val jsonStr = data["data"].let { - if (it is JsonObject) { - it.asJsonObject.toString() - } else { - // 检查字符串是否是合法json,不然qq会闪退 - try { - val str = it.asString - val element = Json.decodeFromString(str) - if (element !is JsonObject) { - return Result.failure(Exception("不合法的JSON字符串")) - } - str - } catch (err: Throwable) { - LogCenter.log(err.stackTraceToString(), Level.ERROR) - return Result.failure(Exception("不合法的JSON字符串")) - } - } - } - val element = MsgElement() - element.elementType = MsgConstant.KELEMTYPEARKSTRUCT - val ark = ArkElement(jsonStr, null, null) - element.arkElement = ark - return Result.success(element) - } - - private suspend fun createTouchElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ): Result { - data.checkAndThrow("id") - GroupSvc.poke(peerId.toLong(), data["id"].asLong) - return Result.failure(ActionMsgException) - } - - private suspend fun createWeatherElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ): Result { - var code = data["code"].asIntOrNull - - if (code == null) { - data.checkAndThrow("city") - val city = data["city"].asString - code = WeatherSvc.searchCity(city).onFailure { - LogCenter.log("无法获取城市天气: $city", Level.ERROR) - }.getOrNull()?.firstOrNull()?.adcode - } - - if (code != null) { - WeatherSvc.fetchWeatherCard(code).onSuccess { - val element = MsgElement() - element.elementType = MsgConstant.KELEMTYPEARKSTRUCT - val share = it["weekStore"] - .asJsonObject["share"] - .asJsonObject["data"].toString() - - element.arkElement = - ArkElement(share, null, MsgConstant.ARKSTRUCTELEMENTSUBTYPEUNKNOWN) - - return Result.success(element) - }.onFailure { - LogCenter.log("无法发送天气分享", Level.ERROR) - } - } - return Result.failure(ActionMsgException) - } - - private suspend fun createReplyElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ): Result { - data.checkAndThrow("id") - val element = MsgElement() - element.elementType = MsgConstant.KELEMTYPEREPLY - val reply = ReplyElement() - - val msgHash = data["id"].asInt - val mapping = MessageHelper.getMsgMappingByHash(msgHash) - ?: return Result.failure(Exception("不存在该消息映射,无法回复消息")) - - reply.replayMsgId = mapping.qqMsgId - reply.sourceMsgIdInRecords = mapping.qqMsgId - - if (reply.replayMsgId == 0L) { - // 貌似获取失败了,555 - LogCenter.log("无法获取被回复消息", Level.ERROR) - } - - if (data.containsKey("text")) { - data.checkAndThrow("qq", "time", "seq") - reply.replayMsgSeq = data["seq"].asLong - reply.sourceMsgText = data["text"].asString - reply.replyMsgTime = data["time"].asLong - reply.senderUid = data["qq"].asString.toLong() - } - element.replyElement = reply - return Result.success(element) - } - - private suspend fun createMusicElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ): Result { - data.checkAndThrow("type") - - when (val type = data["type"].asString) { - "qq" -> { - data.checkAndThrow("id") - val id = data["id"].asString - if (!MusicHelper.tryShareQQMusicById(chatType, peerId.toLong(), msgId, id)) { - LogCenter.log("无法发送QQ音乐分享", Level.ERROR) - } - } - - "163" -> { - data.checkAndThrow("id") - val id = data["id"].asString - if (!MusicHelper.tryShare163MusicById(chatType, peerId.toLong(), msgId, id)) { - LogCenter.log("无法发送网易云音乐分享", Level.ERROR) - } - } - - "custom" -> { - data.checkAndThrow("url", "audio", "title") - ArkMsgSvc.tryShareMusic( - chatType, - peerId.toLong(), - msgId, - ArkAppInfo.QQMusic, - data["title"].asString, - data["singer"].asStringOrNull ?: "", - data["url"].asString, - data["image"].asStringOrNull ?: "", - data["audio"].asString - ) - } - - else -> LogCenter.log("不支持的音乐分享类型: $type", Level.ERROR) - } - - return Result.failure(ActionMsgException) - } - - private suspend fun createLocationElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ): Result { - data.checkAndThrow("lat", "lon") - - val lat = data["lat"].asString.toDouble() - val lon = data["lon"].asString.toDouble() - - LbsSvc.tryShareLocation(chatType, peerId.toLong(), lat, lon).onFailure { - LogCenter.log("无法发送位置分享", Level.ERROR) - } - - return Result.failure(ActionMsgException) - } - - private suspend fun createContactElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ): Result { - data.checkAndThrow("id") - val type = data["type"].asStringOrNull ?: data["kind"].asStringOrNull - val id = data["id"].asString - val elem = MsgElement() - - when (type) { - "private", "qq" -> { - val ark = ArkElement(CardSvc.getSharePrivateArkMsg(id.toLong()), null, null) - elem.arkElement = ark - } - - "group" -> { - val ark = ArkElement(GroupSvc.getShareTroopArkMsg(id.toLong()), null, null) - elem.arkElement = ark - } - - else -> throw IllegalParamsException("type") - } - - elem.elementType = MsgConstant.KELEMTYPEARKSTRUCT - - return Result.success(elem) - } - - private suspend fun createShareElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ): Result { - data.checkAndThrow("title", "url") - - val url = data["url"].asString - val image = if (data.containsKey("image")) { - data["image"].asString - } else { - val startWithPrefix = url.startsWith("http://") || url.startsWith("https://") - val endWithPrefix = url.startsWith("/") - "http://" + url.split("/")[if (startWithPrefix) 2 else 0] + if (!endWithPrefix) { - "/favicon.ico" - } else { - "favicon.ico" - } - } - val title = data["title"].asString - val content = data["content"].asStringOrNull - - val reqBody = oidb_cmd0xdc2.ReqBody() - val info = oidb_cmd0xb77.ReqBody() - info.appid.set(100446242L) - info.app_type.set(1) - info.msg_style.set(0) - info.recv_uin.set(peerId.toLong()) - val clientInfo = oidb_cmd0xb77.ClientInfo() - clientInfo.platform.set(1) - info.client_info.set(clientInfo) - val richMsgBody = oidb_cmd0xb77.RichMsgBody() - richMsgBody.using_ark.set(true) - richMsgBody.title.set(title) - richMsgBody.summary.set(content ?: url) - richMsgBody.brief.set("[分享] $title") - richMsgBody.url.set(url) - richMsgBody.picture_url.set(image) - info.ext_info.set(oidb_cmd0xb77.ExtInfo().also { - it.msg_seq.set(msgId) - }) - info.rich_msg_body.set(richMsgBody) - reqBody.msg_body.set(info) - val sendTo = oidb_cmd0xdc2.BatchSendReq() - when (chatType) { - MsgConstant.KCHATTYPEGROUP -> sendTo.send_type.set(1) - MsgConstant.KCHATTYPEC2C -> sendTo.send_type.set(0) - else -> return createTextElem( - chatType = chatType, - msgId = msgId, - peerId = peerId, - data = JsonObject(mapOf("text" to JsonPrimitive("[分享] $title\n地址: $url"))) - ) - } - sendTo.recv_uin.set(peerId.toLong()) - reqBody.batch_send_req.add(sendTo) - val app = AppRuntimeFetcher.appRuntime as QQAppInterface - val to = ToServiceMsg("mobileqq.service", app.currentAccountUin, "OidbSvc.0xdc2_34") - val oidb = oidb_sso.OIDBSSOPkg() - oidb.uint32_command.set(0xdc2) - oidb.uint32_service_type.set(34) - oidb.bytes_bodybuffer.set(ByteStringMicro.copyFrom(reqBody.toByteArray())) - oidb.str_client_version.set(PlatformUtils.getClientVersion(MobileQQ.getContext())) - to.putWupBuffer(oidb.toByteArray()) - to.addAttribute("req_pb_protocol_flag", true) - app.sendToService(to) - return Result.failure(ActionMsgException) - } - - private suspend fun createAnonymousElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ): Result { - return Result.failure(ActionMsgException) - } - - private suspend fun createPokeElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ): Result { - data.checkAndThrow("type", "id") - val elem = MsgElement() - val face = FaceElement() - face.faceIndex = 0 - face.faceText = "" - face.faceType = 5 - face.packId = null - face.pokeType = data["type"].asInt - face.spokeSummary = "" - face.doubleHit = 0 - face.vaspokeId = data["id"].asInt - face.vaspokeName = "" - face.vaspokeMinver = "" - face.pokeStrength = (data["strength"].asIntOrNull ?: data["cnt"].asIntOrNull - ?: data["count"].asIntOrNull ?: data["time"].asIntOrNull ?: 0).also { - if (it < 0 || it > 3) throw IllegalParamsException("strength") - } - face.msgType = 0 - face.faceBubbleCount = 0 - face.oldVersionStr = "[截一戳]请使用最新版手机QQ体验新功能。" - face.pokeFlag = 0 - elem.elementType = MsgConstant.KELEMTYPEFACE - elem.faceElement = face - return Result.success(elem) - } - - private suspend fun createFaceElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ): Result { - data.checkAndThrow("id") - - val serverId = data["id"].asInt - val big = (data["big"].asBooleanOrNull ?: false) || serverId == 394 - - val elem = MsgElement() - elem.elementType = MsgConstant.KELEMTYPEFACE - val face = FaceElement() - - // 1 old face - // 2 normal face - // 3 super face - // 4 is market face - // 5 is vas poke - face.faceType = if (big) 3 else 2 - face.faceIndex = serverId - face.faceText = QQSysFaceUtil.getFaceDescription(QQSysFaceUtil.convertToLocal(serverId)) - if (serverId == 394) { - face.stickerId = "40" - face.packId = "1" - face.sourceType = 1 - face.stickerType = 3 - face.randomType = 1 - face.resultId = data["result"].asStringOrNull ?: Random.nextInt(1..5).toString() - } else if (big) { - face.imageType = 0 - face.stickerId = "30" - face.packId = "1" - face.sourceType = 1 - face.stickerType = 1 - face.randomType = 1 - } else { - face.imageType = 0 - face.packId = "0" - } - elem.faceElement = face - - return Result.success(elem) - } - - private suspend fun createRpsElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ): Result { - val elem = MsgElement() - elem.elementType = MsgConstant.KELEMTYPEMARKETFACE - val market = MarketFaceElement( - 6, 1, 11415, 3, 0, 200, 200, - "[猜拳]", "83C8A293AE65CA140F348120A77448EE", "7de39febcf45e6db", - null, null, 0, 0, 0, 1, 0, - null, null, null, - "", null, null, - null, null, arrayListOf(MarketFaceSupportSize(200, 200)), null - ) - elem.marketFaceElement = market - return Result.success(elem) - } - - private suspend fun createDiceElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ): Result { - val elem = MsgElement() - elem.elementType = MsgConstant.KELEMTYPEMARKETFACE - val market = MarketFaceElement( - 6, 1, 11464, 3, 0, 200, 200, - "[骰子]", "4823d3adb15df08014ce5d6796b76ee1", "409e2a69b16918f9", - null, null, 0, 0, 0, 1, 0, - null, null, null, // jumpurl - "", null, null, - null, null, arrayListOf(MarketFaceSupportSize(200, 200)), null - ) - elem.marketFaceElement = market - return Result.success(elem) - } - - private suspend fun createMarkdownElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ): Result { - data.checkAndThrow("content") - val elem = MsgElement() - elem.elementType = MsgConstant.KELEMTYPEMARKDOWN - val markdown = MarkdownElement(data["content"].asString) - elem.markdownElement = markdown - return Result.success(elem) - } - - private suspend fun createVideoElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ): Result { - data.checkAndThrow("file") - - val file = data["file"].asString.let { - val md5 = it.replace(regex = "[{}\\-]".toRegex(), replacement = "").split(".")[0].lowercase() - var file = if (md5.length == 32) { - FileUtils.getFileByMd5(it) - } else { - FileUtils.parseAndSave(it) - } - if (!file.exists() && data.containsKey("url")) { - file = FileUtils.parseAndSave(data["url"].asString) - } - return@let file - } - if (!file.exists()) { - throw LogicException("Video(${file.name}) file is not exists, please check your filename.") - } - val elem = MsgElement() - 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!!) - } - - if (ShamrockConfig.enableOldBDH()) { - Transfer with when (chatType) { - MsgConstant.KCHATTYPEGROUP -> Troop(peerId) - MsgConstant.KCHATTYPEC2C -> Private(peerId) - else -> error("Not supported chatType($chatType) for VideoMsg") - } trans VideoResource(file, File(thumbPath.toString())) - } - - 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 - elem.elementType = MsgConstant.KELEMTYPEVIDEO - - return Result.success(elem) - } - - private suspend fun createAtElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result { - if (chatType != MsgConstant.KCHATTYPEGROUP) { - return Result.failure(ActionMsgException) - } - data.checkAndThrow("qq") - - val elem = MsgElement() - val qqStr = data["qq"].asString - - val at = TextElement() - when (qqStr) { - "0", "all" -> { - at.content = "@全体成员" - at.atType = MsgConstant.ATTYPEALL - at.atNtUid = "0" - } - - "admin" -> { - at.content = "@管理员" - at.atRoleId = 1 - at.atType = MsgConstant.ATTYPEROLE - at.atNtUid = "0" - } - - "online" -> { - at.content = "@在线成员" - at.atType = MsgConstant.ATTYPEONLINE - at.atNtUid = "0" - } - - else -> { - val qq = qqStr.toLong() - val name = data["name"].asStringOrNull - if (name == null) { - val info = GroupSvc.getTroopMemberInfoByUinV2(peerId.toLong(), qq, true).onFailure { - LogCenter.log("无法获取群成员信息: $qqStr", Level.ERROR) - }.getOrNull() - if (info != null) { - at.content = "@${ - info.troopnick - .ifNullOrEmpty(info.friendnick) - .ifNullOrEmpty(qqStr) - }" - } else { - at.content = "@$qqStr" - } - } else { - at.content = "@$name" - } - at.atType = MsgConstant.ATTYPEONE - at.atNtUid = ContactHelper.getUidByUinAsync(qq) - } - } - - elem.textElement = at - elem.elementType = MsgConstant.KELEMTYPETEXT - - return Result.success(elem) - } - - private suspend fun createRecordElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ): Result { - var file = data["file"].asStringOrNull?.let { - val md5 = it.replace(regex = "[{}\\-]".toRegex(), replacement = "") - .replace(" ", "") - .split(".")[0].lowercase() - if (md5.length == 32) { - LocalCacheHelper.getCachePttFile(md5) - } else { - FileUtils.parseAndSave(it) - } - } - if (file == null || (!file.exists() && data.containsKey("url"))) { - file = FileUtils.parseAndSave(data["url"].asString) - } - if (!file.exists()) { - return Result.failure(LogicException("Voice(${file.name}) file is not exists, please check your filename.")) - } - val isMagic = data["magic"].asStringOrNull == "1" - - val ptt = PttElement() - - when (AudioUtils.getMediaType(file)) { - MediaType.Silk -> { - LogCenter.log({ "Silk: $file" }, Level.DEBUG) - ptt.formatType = MsgConstant.KPTTFORMATTYPESILK - ptt.duration = QRoute.api(IAIOPttApi::class.java) - .getPttFileDuration(file.absolutePath) - } - - MediaType.Amr -> { - LogCenter.log({ "Amr: $file" }, Level.DEBUG) - ptt.duration = AudioUtils.getDurationSec(file) - ptt.formatType = MsgConstant.KPTTFORMATTYPEAMR - } - - MediaType.Pcm -> { - LogCenter.log({ "Pcm To Silk: $file" }, Level.DEBUG) - val result = AudioUtils.pcmToSilk(file) - ptt.duration = (result.second * 0.001).roundToInt() - file = result.first - ptt.formatType = MsgConstant.KPTTFORMATTYPESILK - } - - else -> { - LogCenter.log({ "Audio To SILK: $file" }, Level.DEBUG) - val result = AudioUtils.audioToSilk(file) - ptt.duration = runCatching { - QRoute.api(IAIOPttApi::class.java) - .getPttFileDuration(result.second.absolutePath) - }.getOrElse { - result.first - } - file = result.second - ptt.formatType = MsgConstant.KPTTFORMATTYPESILK - } - } - - val elem = MsgElement() - elem.elementType = MsgConstant.KELEMTYPEPTT - ptt.md5HexStr = QQNTWrapperUtil.CppProxy.genFileMd5Hex(file.absolutePath) - - if (ShamrockConfig.enableOldBDH()) { - if (!(Transfer with when (chatType) { - MsgConstant.KCHATTYPEGROUP -> Troop(peerId) - MsgConstant.KCHATTYPEC2C -> Private(peerId) - MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> Private(peerId) - else -> error("Not supported chatType($chatType) for RecordMsg") - } trans VoiceResource(file)) - ) { - return Result.failure(RuntimeException("上传语音失败: $file")) - } - - ptt.filePath = file.absolutePath - } else { - 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) != file.length()) { - QQNTWrapperUtil.CppProxy.copyFile(file.absolutePath, originalPath) - } - if (originalPath != null) { - ptt.filePath = originalPath - } else { - ptt.filePath = file.absolutePath - } - } - - ptt.canConvert2Text = true - ptt.fileId = 0 - ptt.fileUuid = "" - ptt.text = "" - - if (!isMagic) { - ptt.voiceType = MsgConstant.KPTTVOICETYPESOUNDRECORD - ptt.voiceChangeType = MsgConstant.KPTTVOICECHANGETYPENONE - } else { - ptt.voiceType = MsgConstant.KPTTVOICETYPEVOICECHANGE - ptt.voiceChangeType = MsgConstant.KPTTVOICECHANGETYPEECHO - } - - elem.pttElement = ptt - - return Result.success(elem) - } - - private suspend fun createImageElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ): Result { - val type = data["type"].asStringOrNull ?: "" - val isOriginal = type == "original" - val isFlash = type == "flash" - val filePath = data["file"].asStringOrNull - val url = data["url"].asStringOrNull - var file: File? = null - if (filePath != null) { - val md5 = filePath - .replace(regex = "[{}\\-]".toRegex(), replacement = "") - .split(".")[0].lowercase() - file = if (md5.length == 32) { - FileUtils.getFileByMd5(md5) - } else { - FileUtils.parseAndSave(filePath) - } - } - if ((file == null || !file.exists()) && url != null) { - file = FileUtils.parseAndSave(url) - } - if (file?.exists() == false) { - throw LogicException("Image(${file.name}) file is not exists, please check your filename.") - } - requireNotNull(file) - - if (ShamrockConfig.enableOldBDH()) { - Transfer with when (chatType) { - MsgConstant.KCHATTYPEGROUP -> Troop(peerId) - MsgConstant.KCHATTYPEC2C -> Private(peerId) - else -> error("Not supported chatType($chatType) for PictureMsg") - } trans PictureResource(file) - } - - val elem = MsgElement() - elem.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 = isOriginal - pic.picType = FileUtils.getPicType(file) - // GO-CQHTTP扩展参数 支持 - pic.picSubType = data["subType"].asIntOrNull ?: 0 - pic.isFlashPic = isFlash - - //if (PlatformUtils.getQQVersionCode() >= PlatformUtils.QQ_9_0_8_VER && !ShamrockConfig.enableOldBDH()) { - // pic.storeID = 1 - //} - - elem.picElement = pic - - return Result.success(elem) - } - - private suspend fun createTextElem( - chatType: Int, - msgId: Long, - peerId: String, - data: JsonObject - ): Result { - data.checkAndThrow("text") - val elem = MsgElement() - elem.elementType = MsgConstant.KELEMTYPETEXT - val text = TextElement() - text.content = data["text"].asString - elem.textElement = text - return Result.success(elem) - } - - private fun JsonObject.checkAndThrow(vararg key: String) { - key.forEach { - if (!containsKey(it)) throw ParamsException(it) - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/structures/Files.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/structures/Files.kt deleted file mode 100644 index 8c3eb363..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/structures/Files.kt +++ /dev/null @@ -1,53 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet.structures - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class FileUrl( - @SerialName("url") val url: String, -) - -@Serializable -data class GroupFileList( - @SerialName("files") val files: List, - @SerialName("folders") val folders: List, -) - -@Serializable -data class FileInfo( - @SerialName("group_id") val groupId: Long, - @SerialName("file_id") val fileId: String, - @SerialName("file_name") val fileName: String, - @SerialName("file_size") val fileSize: Long, - @SerialName("busid") val busid: Int, - @SerialName("upload_time") val uploadTime: Int, - @SerialName("dead_time") val deadTime: Int, - @SerialName("modify_time") val modifyTime: Int, - @SerialName("download_times") val downloadTimes: Int, - @SerialName("uploader") val uploadUin: Long, - @SerialName("upload_name") val uploadNick: String, - @SerialName("sha") val sha: String, - @SerialName("sha3") val sha3: String, - @SerialName("md5") val md5: String, - -) - -@Serializable -data class FolderInfo( - @SerialName("group_id") val groupId: Long, - @SerialName("folder_id") val folderId: String, - @SerialName("folder_name") val folderName: String, - @SerialName("total_file_count") val totalFileCount: Int, - @SerialName("create_time") val createTime: Int, - @SerialName("creator") val creator: Long, - @SerialName("creator_name") val creatorNick: String, -) - -@Serializable -data class FileSystemInfo( - @SerialName("file_count") val fileCount: Int, - @SerialName("limit_count") val fileLimitCount: Int, - @SerialName("used_space") val usedSpace: Long, - @SerialName("total_space") val totalSpace: Long, -) \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/structures/Group.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/structures/Group.kt deleted file mode 100644 index 83b18b72..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/structures/Group.kt +++ /dev/null @@ -1,30 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet.structures - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -internal data class ProhibitedMemberInfo( - @SerialName("user_id") val memberUin: Long, - @SerialName("time") val shutuptimestap: Int -) - -@Serializable -internal data class GroupAtAllRemainInfo( - @SerialName("can_at_all") val canAtAll: Boolean, - @SerialName("remain_at_all_count_for_group") val remainAtAllCountForGroup: Int, - @SerialName("remain_at_all_count_for_uin") val remainAtAllCountForUin: Int -) - -@Serializable -internal data class NotJoinedGroupInfo( - @SerialName("group_id") val groupId: Long, - @SerialName("max_member_cnt") val maxMember: Int, - @SerialName("member_count") val memberCount: Int, - @SerialName("group_name") val groupName: String, - @SerialName("group_desc") val groupDesc: String, - @SerialName("owner") val owner: Long, - @SerialName("create_time") val createTime: Long, - @SerialName("group_flag") val groupFlag: Int, - @SerialName("group_flag_ext") val groupFlagExt: Int, -) \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/structures/Guild.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/structures/Guild.kt deleted file mode 100644 index f18dde05..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/structures/Guild.kt +++ /dev/null @@ -1,79 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet.structures - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import moe.fuqiuluo.symbols.Protobuf - -@Serializable -data class GuildInfo( - @SerialName("guild_id") var guildId: Long, - @SerialName("guild_name") var guildName: String, - @SerialName("guild_display_id") var guildDisplayId: String, - @SerialName("profile") var profile: String, - @SerialName("status") var status: GuildStatus, - @SerialName("owner_id") var ownerId: Long, - @SerialName("shutup_expire_time") var shutUpTime: Long, - @SerialName("allow_search") var allowSearch: Boolean -) - -@Serializable -data class GuildStatus( - @SerialName("is_enable") var isEnable: Boolean, - @SerialName("is_banned") var isBanned: Boolean, - @SerialName("is_frozen") var isFrozen: Boolean -) - -@Serializable -data class GProChannelInfo( - @SerialName("owner_guild_id") val ownerGuildId: ULong, - @SerialName("channel_id") val channelId: Long, - @SerialName("channel_uin") val channelUin: Long, - @SerialName("guild_id") val guildId: String, - @SerialName("channel_type") val channelType: Int, - @SerialName("channel_name") val channelName: String, - @SerialName("create_time") val createTime: Long, - @SerialName("max_member_count") val maxMemberCount: Int, - @SerialName("creator_tiny_id") val creatorTinyId: Long, - @SerialName("talk_permission") val talkPermission: Int, - @SerialName("visible_type") val visibleType: Int, - @SerialName("current_slow_mode") val currentSlowMode: Int, - @SerialName("slow_modes") val slowModes: List, - @SerialName("icon_url") val appIconUrl: String? = null, - @SerialName("jump_switch") val jumpSwitch: Int = Int.MIN_VALUE, - @SerialName("jump_type") val jumpType: Int = Int.MIN_VALUE, - @SerialName("jump_url") val jumpUrl: String? = null, - @SerialName("category_id") val categoryId: Long = Long.MIN_VALUE, - @SerialName("my_talk_permission") val myTalkPermission: Int = Int.MIN_VALUE, -) - -@Serializable -data class SlowModeInfo( - @SerialName("slow_mode_key") val slowModeKey: Int, - @SerialName("slow_mode_text") val slowModeText: String, - @SerialName("speak_frequency") val speakFrequency: Int, - @SerialName("slow_mode_circle") val slowModeCircle: Int -) - -@Serializable -data class GetGuildMemberListNextToken( - @SerialName("start_index") val startIndex: Long, - @SerialName("role_index") val roleIndex: Long, - @SerialName("seq") val seq: Int, - @SerialName("finish") val finish: Boolean -): Protobuf - -@Serializable -data class GuildMemberInfo( - @SerialName("tiny_id") val tinyId: Long, - @SerialName("title") val title: String, - @SerialName("nickname") val nickname: String, - @SerialName("role_id") val roleId: Long, - @SerialName("role_name") val roleName: String, - @SerialName("role_color") val roleColor: Long, - @SerialName("join_time") val joinTime: Long, - @SerialName("robot_type") val robotType: Int, - @SerialName("type") val type: Int, - @SerialName("in_black") val inBlack: Boolean, - @SerialName("platform") val platform: Int, -) - diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/structures/RichMedia.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/structures/RichMedia.kt deleted file mode 100644 index ab203781..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/structures/RichMedia.kt +++ /dev/null @@ -1,20 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet.structures - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class UploadResult( - @SerialName("files") val files: List -) - -@Serializable -data class CommFileInfo( - @SerialName("mode_id") val modeId: Long, - @SerialName("name") val fileName: String, - @SerialName("size") val fileSize: Long, - @SerialName("md5") val md5: String, - @SerialName("uuid") val uuid: String, - @SerialName("sub_id") val subId: String, - @SerialName("sha") val sha: String, -) \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/FileTransfer.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/FileTransfer.kt deleted file mode 100644 index ecda50b2..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/FileTransfer.kt +++ /dev/null @@ -1,190 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) - -package moe.fuqiuluo.qqinterface.servlet.transfile - -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 moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher -import mqq.app.AppRuntime -import java.io.File -import kotlin.coroutines.resume -import kotlin.math.abs -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 = AppRuntimeFetcher.appRuntime - 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 = AppRuntimeFetcher.appRuntime - 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 abs(Random.nextInt()).toLong() - return uniseq - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/NtV2RichMediaSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/NtV2RichMediaSvc.kt deleted file mode 100644 index 8774f7fd..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/NtV2RichMediaSvc.kt +++ /dev/null @@ -1,537 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet.transfile - -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.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 kotlinx.atomicfu.atomic -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.withTimeoutOrNull -import moe.fuqiuluo.qqinterface.servlet.BaseSvc -import moe.fuqiuluo.qqinterface.servlet.TicketSvc -import moe.fuqiuluo.qqinterface.servlet.transfile.data.TryUpPicData -import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig -import moe.fuqiuluo.shamrock.tools.hex2ByteArray -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.shamrock.xposed.helper.NTServiceFetcher -import moe.fuqiuluo.shamrock.xposed.helper.msgService -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 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 - -internal object NtV2RichMediaSvc: BaseSvc() { - private val requestIdSeq = atomic(2L) - - fun fetchGroupResUploadTo(): String { - return ShamrockConfig.getUpResGroup().ifEmpty { "100000000" } - } - - suspend fun tryUploadResourceByNt( - chatType: Int, - elementType: Int, - resources: ArrayList, - timeout: Duration, - retryCnt: Int = 5 - ): Result> { - 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, - timeout: Duration - ): Result> { - require(resources.size in 1 .. 10) { "imageFiles.size() must be in 1 .. 10" } - - val messages = 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 - } - - /*MsgConstant.KELEMTYPEFILE -> { - require(resources.size == 1) // 文件只能单个上传 - val fileElement = FileElement() - fileElement.fileMd5 = "" - fileElement.fileName = file.name - fileElement.filePath = file.absolutePath - fileElement.fileSize = file.length() - fileElement.picWidth = 0 - fileElement.picHeight = 0 - fileElement.videoDuration = 0 - fileElement.picThumbPath = HashMap() - fileElement.expireTime = 0L - fileElement.fileSha = "" - fileElement.fileSha3 = "" - fileElement.file10MMd5 = "" - when (TransfileHelper.getExtensionId(file.name)) { - 0 -> { - val wh = QRoute.api(IMsgUtilApi::class.java) - .getPicSizeByPath(file.absolutePath) - fileElement.picWidth = wh.first - fileElement.picHeight = wh.second - fileElement.picThumbPath[750] = file.absolutePath - } - 2 -> { - val thumbPic = FileUtils.getFileByMd5(MD5.genFileMd5Hex(file.absolutePath)) - withContext(Dispatchers.IO) { - val fileOutputStream = FileOutputStream(thumbPic) - val retriever = MediaMetadataRetriever() - retriever.setDataSource(fileElement.filePath) - retriever.frameAtTime?.compress(Bitmap.CompressFormat.JPEG, 60, fileOutputStream) - fileOutputStream.flush() - fileOutputStream.close() - } - val options = BitmapFactory.Options() - BitmapFactory.decodeFile(thumbPic.absolutePath, options) - fileElement.picHeight = options.outHeight - fileElement.picWidth = options.outWidth - fileElement.picThumbPath = hashMapOf(750 to thumbPic.absolutePath) - } - } - elem.fileElement = fileElement - }*/ - 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, TicketSvc.getUin()) - else -> Contact(chatType, fetchGroupResUploadTo(), null) - } - val result = mutableListOf() - withTimeoutOrNull(timeout) { - suspendCancellableCoroutine { - val uniseq = MessageHelper.generateMsgId(chatType) - RichMediaUploadHandler.registerListener(uniseq.qqMsgId) upload@{ - if (uniseq.qqMsgId == msgId) { - result.add(commonFileInfo) - } - if (result.size == resources.size) { - it.resume(true) - return@upload true - } - return@upload false - } - MessageHelper.sendMessageWithMsgId( - contact = contact, - message = ArrayList(messages), - uniseq = uniseq.qqMsgId - ) { _, _ -> - if (contact.chatType == MsgConstant.KCHATTYPEGROUP && contact.peerUid == "100000000") { - val kernelService = NTServiceFetcher.kernelService - val sessionService = kernelService.wrapperSession - val msgService = sessionService.msgService - msgService.deleteMsg(contact, arrayListOf(uniseq.qqMsgId), null) - } - } - it.invokeOnCancellation { - RichMediaUploadHandler.removeListener(uniseq.qqMsgId) - } - } - } - - 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 { - 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 buffer = sendOidbAW("OidbSvcTrpcTcp.0x11c5_200", 4549, 200, req, true)?.slice(4) - buffer?.decodeProtobuf()?.buffer?.decodeProtobuf()?.download?.rkeyParam?.let { - return Result.success(it) - } - }.onFailure { - return Result.failure(it) - } - return Result.failure(Exception("unable to get c2c nt pic")) - } - - suspend fun requestUploadNtPic( - file: File, - md5: String, - sha: String, - name: String, - width: UInt, - height: UInt, - retryCnt: Int, - chatType: Int = MsgConstant.KCHATTYPEGROUP, - sceneBuilder: suspend SceneInfo.() -> Unit - ): Result { - return runCatching { - requestUploadNtPic(file, md5, sha, name, width, height, chatType, sceneBuilder).getOrThrow() - }.onFailure { - if (retryCnt > 0) { - return requestUploadNtPic(file, md5, sha, name, width, height, retryCnt - 1, chatType, sceneBuilder) - } - } - } - - private suspend fun requestUploadNtPic( - file: File, - md5: String, - sha: String, - name: String, - width: UInt, - height: UInt, - chatType: Int, - sceneBuilder: suspend SceneInfo.() -> Unit - ): Result { - val req = NtV2RichMediaReq( - head = MultiMediaReqHead( - commonHead = CommonHead( - requestId = requestIdSeq.incrementAndGet().toULong(), - cmd = 100u - ), - sceneInfo = SceneInfo( - requestType = 2u, - businessType = 1u, - ).apply { - sceneBuilder() - }, - clientMeta = ClientMeta(2u) - ), - upload = UploadReq( - listOf(UploadInfo( - FileInfo( - fileSize = file.length().toULong(), - md5 = md5, - sha1 = sha, - name = name, - fileType = FileType( - fileType = 1u, - picFormat = 1000u, - videoFormat = 0u, - voiceFormat = 0u - ), - width = width, - height = height, - time = 0u, - original = 1u - ), - subFileType = 0u - )), - tryFastUploadCompleted = true, - srvSendMsg = false, - clientRandomId = Random.nextULong(), - compatQMsgSceneType = 2u, - clientSeq = Random.nextUInt(), - noNeedCompatMsg = true - ) - ).toByteArray() - val buffer = when (chatType) { - MsgConstant.KCHATTYPEGROUP -> { - sendOidbAW("OidbSvcTrpcTcp.0x11c4_100", 4548, 100, req, true, timeout = 3_000)?.slice(4) - ?: return Result.failure(Exception("no response: timeout")) - } - MsgConstant.KCHATTYPEC2C -> { - sendOidbAW("OidbSvcTrpcTcp.0x11c5_100", 4549, 100, req, true, timeout = 3_000)?.slice(4) - ?: return Result.failure(Exception("no response: timeout")) - } - - else -> return Result.failure(Exception("unknown chat type: $chatType")) - } - val rspBuffer = buffer.decodeProtobuf().buffer - val rsp = rspBuffer.decodeProtobuf() - 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 { - return runCatching { - val rspBuffer = sendBufferAW("ImgStore.GroupPicUp", true, Cmd0x388ReqBody( - netType = 3, - subCmd = 1, - msgTryUpImg = arrayListOf( - TryUpImgReq( - groupCode = groupId.toLong(), - srcUin = TicketSvc.getLongUin(), - 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())!! - val rsp = rspBuffer.decodeProtobuf() - .msgTryUpImgRsp!!.first() - TryUpPicData( - uKey = rsp.ukey, - exist = rsp.fileExist, - fileId = rsp.fileId.toULong(), - upIp = rsp.upIp, - upPort = rsp.upPort - ) - } - } -} - diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/RichMediaUploadHandler.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/RichMediaUploadHandler.kt deleted file mode 100644 index e5f01ae3..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/RichMediaUploadHandler.kt +++ /dev/null @@ -1,27 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet.transfile - -import com.tencent.qqnt.kernel.nativeinterface.FileTransNotifyInfo - -internal object RichMediaUploadHandler { - private val listeners by lazy { - mutableMapOf Boolean>() - } - - fun registerListener(key: Long, value: FileTransNotifyInfo.() -> Boolean) { - listeners[key] = value - } - - fun removeListener(key: Long) { - listeners.remove(key) - } - - fun notify(info: FileTransNotifyInfo): Boolean { - listeners[info.msgId]?.let { - if (it(info)) { - listeners.remove(info.msgId) - return true - } - } - return false - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/RichProtoSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/RichProtoSvc.kt deleted file mode 100644 index f37a6cca..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/RichProtoSvc.kt +++ /dev/null @@ -1,431 +0,0 @@ -@file:OptIn(ExperimentalSerializationApi::class) -package moe.fuqiuluo.qqinterface.servlet.transfile - -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.qqinterface.servlet.BaseSvc -import moe.fuqiuluo.qqinterface.servlet.transfile.NtV2RichMediaSvc.getNtPicRKey -import moe.fuqiuluo.shamrock.helper.ContactHelper -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.tools.hex2ByteArray -import moe.fuqiuluo.shamrock.tools.slice -import moe.fuqiuluo.shamrock.tools.toHexString -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher -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 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: BaseSvc() { - suspend fun getGuildFileDownUrl(peerId: String, channelId: String, fileId: String, bizId: Int): String { - val buffer = 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()) ?: return "" - val body = oidb_sso.OIDBSSOPkg() - body.mergeFrom(buffer.slice(4)) - body.bytes_bodybuffer - .get().toByteArray() - .decodeProtobuf() - .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 buffer = 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()) ?: return "" - val body = oidb_sso.OIDBSSOPkg() - body.mergeFrom(buffer.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 buffer = 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 (buffer == null) { - if (retryCnt < 5) { - return getC2CFileDownUrl(fileId, subId, retryCnt + 1) - } - return "" - } else { - val body = oidb_sso.OIDBSSOPkg() - body.mergeFrom(buffer.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=")) { - 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()) 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=")) { - 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 runtime = AppRuntimeFetcher.appRuntime - val richProtoReq = RichProto.RichProtoReq() - val downReq: RichProto.RichProtoReq.ShortVideoDownReq = RichProto.RichProtoReq.ShortVideoDownReq() - downReq.selfUin = runtime.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 = runtime.getRuntimeService(IProtoReqManager::class.java, "all") - RichProtoProc.procRichProtoReq(richProtoReq) - } - } - - suspend fun getGroupVideoDownUrl( - peerId: String, - md5: ByteArray, - fileUUId: String - ): String { - return suspendCancellableCoroutine { - val runtime = AppRuntimeFetcher.appRuntime - val richProtoReq = RichProto.RichProtoReq() - val downReq: RichProto.RichProtoReq.ShortVideoDownReq = RichProto.RichProtoReq.ShortVideoDownReq() - downReq.selfUin = runtime.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 = runtime.getRuntimeService(IProtoReqManager::class.java, "all") - RichProtoProc.procRichProtoReq(richProtoReq) - } - } - - suspend fun getC2CPttDownUrl( - peerId: String, - fileUUId: String - ): String { - return suspendCancellableCoroutine { - val runtime = AppRuntimeFetcher.appRuntime - val richProtoReq = RichProto.RichProtoReq() - val pttDownReq: RichProto.RichProtoReq.C2CPttDownReq = RichProto.RichProtoReq.C2CPttDownReq() - pttDownReq.selfUin = runtime.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 = runtime.getRuntimeService(IProtoReqManager::class.java, "all") - RichProtoProc.procRichProtoReq(richProtoReq) - } - } - - suspend fun getGroupPttDownUrl( - peerId: String, - md5: ByteArray, - groupFileKey: String - ): String { - return suspendCancellableCoroutine { - val runtime = AppRuntimeFetcher.appRuntime - val richProtoReq = RichProto.RichProtoReq() - val groupPttDownReq: RichProto.RichProtoReq.GroupPttDownReq = RichProto.RichProtoReq.GroupPttDownReq() - groupPttDownReq.selfUin = runtime.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 = runtime.getRuntimeService(IProtoReqManager::class.java, "all") - RichProtoProc.procRichProtoReq(richProtoReq) - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/Transfer.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/Transfer.kt deleted file mode 100644 index d1b7c4a3..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/Transfer.kt +++ /dev/null @@ -1,142 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet.transfile - -import com.tencent.mobileqq.data.MessageForShortVideo -import com.tencent.mobileqq.data.MessageRecord -import com.tencent.mobileqq.transfile.FileMsg -import com.tencent.mobileqq.transfile.TransferRequest -import moe.fuqiuluo.shamrock.utils.MD5 -import java.io.File -import moe.fuqiuluo.qqinterface.servlet.transfile.data.ResourceType.* -import moe.fuqiuluo.qqinterface.servlet.transfile.data.ContactType -import moe.fuqiuluo.qqinterface.servlet.transfile.data.PictureResource -import moe.fuqiuluo.qqinterface.servlet.transfile.data.Resource -import moe.fuqiuluo.qqinterface.servlet.transfile.data.ResourceType -import moe.fuqiuluo.qqinterface.servlet.transfile.data.TransTarget -import moe.fuqiuluo.qqinterface.servlet.transfile.data.VideoResource -import moe.fuqiuluo.qqinterface.servlet.transfile.data.VoiceResource - -internal object Transfer: FileTransfer() { - private val ROUTE = mapOf Boolean>>( - ContactType.TROOP to mapOf( - Picture to { uploadGroupPic(id, (it as PictureResource).src, mRec) }, - Voice to { uploadGroupVoice(id, (it as VoiceResource).src) }, - Video to { uploadGroupVideo(id, (it as VideoResource).src, it.thumb) }, - - ), - ContactType.PRIVATE to mapOf( - Picture to { uploadC2CPic(id, (it as PictureResource).src, mRec) }, - Voice to { uploadC2CVoice(id, (it as VoiceResource).src) }, - Video to { uploadC2CVideo(id, (it as VideoResource).src, it.thumb) }, - ) - ) - - suspend fun uploadC2CVideo( - userId: String, - file: File, - thumb: File, - wait: Boolean = true - ): Boolean { - return transC2CResource(userId, file, FileMsg.TRANSFILE_TYPE_SHORT_VIDEO_C2C, BUSI_TYPE_SHORT_VIDEO, wait) { - it.mSourceVideoCodecFormat = VIDEO_FORMAT_MP4 - it.mRec = MessageForShortVideo().also { - it.busiType = BUSI_TYPE_SHORT_VIDEO - } - it.mThumbPath = thumb.absolutePath - it.mThumbMd5 = MD5.genFileMd5Hex(thumb.absolutePath) - } - } - - suspend fun uploadGroupVideo( - groupId: String, - file: File, - thumb: File, - wait: Boolean = true - ): Boolean { - return transTroopResource(groupId, file, FileMsg.TRANSFILE_TYPE_SHORT_VIDEO_TROOP, BUSI_TYPE_SHORT_VIDEO, wait) { - it.mSourceVideoCodecFormat = VIDEO_FORMAT_MP4 - it.mRec = MessageForShortVideo().also { - it.busiType = BUSI_TYPE_SHORT_VIDEO - } - it.mThumbPath = thumb.absolutePath - it.mThumbMd5 = MD5.genFileMd5Hex(thumb.absolutePath) - } - } - - suspend fun uploadC2CVoice( - userId: String, - file: File, - wait: Boolean = true - ): Boolean { - return transC2CResource(userId, file, FileMsg.TRANSFILE_TYPE_PTT, 1002, wait) { - it.mPttUploadPanel = 3 - it.mPttCompressFinish = true - it.mIsPttPreSend = true - } - } - - suspend fun uploadGroupVoice( - groupId: String, - file: File, - wait: Boolean = true - ): Boolean { - return transTroopResource(groupId, file, FileMsg.TRANSFILE_TYPE_PTT, 1002, wait) { - it.mPttUploadPanel = 3 - it.mPttCompressFinish = true - it.mIsPttPreSend = true - } - } - - suspend fun uploadC2CPic( - peerId: String, - file: File, - record: MessageRecord? = null, - wait: Boolean = true - ): Boolean { - return transC2CResource(peerId, file, FileMsg.TRANSFILE_TYPE_PIC, SEND_MSG_BUSINESS_TYPE_PIC_CAMERA, wait) { - val picUpExtraInfo = TransferRequest.PicUpExtraInfo() - picUpExtraInfo.mIsRaw = false - picUpExtraInfo.mUinType = FileMsg.UIN_BUDDY - it.mPicSendSource = 8 - it.mExtraObj = picUpExtraInfo - it.mIsPresend = true - it.delayShowProgressTimeInMs = 2000 - it.mRec = record - } - } - - suspend fun uploadGroupPic( - groupId: String, - file: File, - record: MessageRecord? = null, - wait: Boolean = true - ): Boolean { - return transTroopResource(groupId, file, FileMsg.TRANSFILE_TYPE_PIC, SEND_MSG_BUSINESS_TYPE_PIC_CAMERA, wait) { - val picUpExtraInfo = TransferRequest.PicUpExtraInfo() - picUpExtraInfo.mIsRaw = false - picUpExtraInfo.mUinType = FileMsg.UIN_TROOP - it.mPicSendSource = 8 - it.delayShowProgressTimeInMs = 2000 - it.mExtraObj = picUpExtraInfo - it.mRec = record - } - } - - operator fun get(contactType: ContactType, resourceType: ResourceType): suspend TransTarget.(Resource) -> Boolean { - return (ROUTE[contactType] ?: error("unsupported contact type: $contactType"))[resourceType] - ?: error("Unsupported resource type: $resourceType") - } -} - -internal suspend infix fun TransferTaskBuilder.trans(res: Resource): Boolean { - return Transfer[contact.type, res.type](contact, res) -} - -internal class TransferTaskBuilder { - lateinit var contact: TransTarget -} - -internal infix fun Transfer.with(contact: TransTarget): TransferTaskBuilder { - return TransferTaskBuilder().also { - it.contact = contact - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/data/Contact.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/data/Contact.kt deleted file mode 100644 index 382242ff..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/data/Contact.kt +++ /dev/null @@ -1,29 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet.transfile.data - -import com.tencent.mobileqq.data.MessageRecord - -internal enum class ContactType { - TROOP, - PRIVATE, -} - -internal interface TransTarget { - val id: String - val type: ContactType - - val mRec: MessageRecord? -} - -internal class Troop( - override val id: String, - override val mRec: MessageRecord? = null -): TransTarget { - override val type: ContactType = ContactType.TROOP -} - -internal class Private( - override val id: String, - override val mRec: MessageRecord? = null -): TransTarget { - override val type: ContactType = ContactType.PRIVATE -} diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/data/Resource.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/data/Resource.kt deleted file mode 100644 index f8d82fe0..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/data/Resource.kt +++ /dev/null @@ -1,31 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet.transfile.data - -import java.io.File - -internal enum class ResourceType { - Picture, - Video, - Voice -} - -internal interface Resource { - val type: ResourceType -} - -internal data class PictureResource( - val src: File -): Resource { - override val type = ResourceType.Picture -} - -internal data class VideoResource( - val src: File, val thumb: File -): Resource { - override val type = ResourceType.Video -} - -internal data class VoiceResource( - val src: File -): Resource { - override val type = ResourceType.Voice -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/data/RichMedia.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/data/RichMedia.kt deleted file mode 100644 index 18b3ef24..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/data/RichMedia.kt +++ /dev/null @@ -1,13 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet.transfile.data - -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? = null, - @SerialName("up_port") var upPort: ArrayList? = null, -) \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/ContactHelper.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/ContactHelper.kt deleted file mode 100644 index 8bab2091..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/ContactHelper.kt +++ /dev/null @@ -1,55 +0,0 @@ -package moe.fuqiuluo.shamrock.helper - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import kotlinx.coroutines.suspendCancellableCoroutine -import moe.fuqiuluo.qqinterface.servlet.FriendSvc -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher -import kotlin.coroutines.resume - -internal object ContactHelper { - suspend fun getUinByUidAsync(uid: String): String { - if (uid.isBlank() || uid == "0") { - return "0" - } - - val kernelService = NTServiceFetcher.kernelService - val sessionService = kernelService.wrapperSession - - return suspendCancellableCoroutine { continuation -> - sessionService.uixConvertService.getUin(hashSetOf(uid)) { - continuation.resume(it) - } - }[uid]?.toString() ?: "0" - } - - suspend fun getUidByUinAsync(peerId: Long): String { - val kernelService = NTServiceFetcher.kernelService - val sessionService = kernelService.wrapperSession - return suspendCancellableCoroutine { continuation -> - sessionService.uixConvertService.getUid(hashSetOf(peerId)) { - continuation.resume(it) - } - }[peerId]!! - } - - /** - * 检查联系人是否可用, 每次都刷新,性能有损耗 - */ - suspend fun checkContactAvailable(chatType: Int, peerId: String): Boolean { - return when(chatType) { - MsgConstant.KCHATTYPEGROUP -> { - GroupSvc.getGroupList(true).getOrNull()?.find { - it.troopcode == peerId - } != null - } - - MsgConstant.KCHATTYPEC2C -> { - FriendSvc.getFriendList(true).getOrNull()?.find { - it.uin == peerId - } != null - } - else -> error("unknown chat type: $chatType") - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/MessageHelper.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/MessageHelper.kt deleted file mode 100644 index 0b990e48..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/MessageHelper.kt +++ /dev/null @@ -1,431 +0,0 @@ -package moe.fuqiuluo.shamrock.helper - -import com.tencent.mobileqq.qroute.QRoute -import com.tencent.qqnt.kernel.nativeinterface.Contact -import com.tencent.qqnt.kernel.nativeinterface.IOperateCallback -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import com.tencent.qqnt.kernel.nativeinterface.MsgElement -import com.tencent.qqnt.msg.api.IMsgService -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.withTimeoutOrNull -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.jsonObject -import moe.fuqiuluo.qqinterface.servlet.MsgSvc -import moe.fuqiuluo.qqinterface.servlet.msg.maker.ElemMaker -import moe.fuqiuluo.qqinterface.servlet.msg.maker.NtMsgElementMaker -import moe.fuqiuluo.shamrock.helper.db.MessageDB -import moe.fuqiuluo.shamrock.helper.db.MessageMapping -import moe.fuqiuluo.shamrock.remote.structures.SendMsgResult -import moe.fuqiuluo.shamrock.tools.* -import protobuf.message.Elem -import protobuf.message.RichText -import kotlin.coroutines.resume -import kotlin.math.abs -import kotlin.time.Duration.Companion.seconds - -internal object MessageHelper { - suspend fun sendMessageWithoutMsgId( - chatType: Int, - peerId: String, - message: String, - callback: IOperateCallback, - fromId: String = peerId - ): SendMsgResult { - val uniseq = generateMsgId(chatType) - val msg = messageArrayToMsgElements(chatType, uniseq.qqMsgId, peerId, decodeCQCode(message)).also { - if (it.second.isEmpty() && !it.first) { - error("消息合成失败,请查看日志或者检查输入。") - } else if (it.second.isEmpty()) { - return uniseq.copy(msgHashId = 0, msgTime = System.currentTimeMillis()) - } - }.second.filter { - it.elementType != -1 - } as ArrayList - return sendMessageWithoutMsgId(chatType, peerId, msg, fromId, callback) - } - - suspend fun resendMsg( - chatType: Int, - peerId: String, - fromId: String, - msgId: Long, - retryCnt: Int, - msgHashId: Int - ): Result { - val contact = generateContact(chatType, peerId, fromId) - return resendMsg(contact, msgId, retryCnt, msgHashId) - } - - suspend fun resendMsg(contact: Contact, msgId: Long, retryCnt: Int, msgHashId: Int): Result { - if (retryCnt < 0) return Result.failure(SendMsgException("消息发送超时次数过多")) - val service = QRoute.api(IMsgService::class.java) - val result = withTimeoutOrNull(15.seconds) { - val resendRet = suspendCancellableCoroutine { - service.resendMsg(contact, msgId) { result, _ -> - it.resume(result) - } - } - if (resendRet != 0 && - resendRet != 4 // 使用OldBDH 100%触发 - ) { - resendMsg(contact, msgId, retryCnt - 1, msgHashId) - } else { - Result.success(SendMsgResult(msgHashId, msgId, System.currentTimeMillis())) - } - } - return result ?: resendMsg(contact, msgId, retryCnt - 1, msgHashId) - } - - @OptIn(DelicateCoroutinesApi::class) - suspend fun sendMessageWithoutMsgId( - chatType: Int, - peerId: String, - message: JsonArray, - fromId: String = peerId, - callback: IOperateCallback - ): Result { - val uniseq = generateMsgId(chatType) - val msg = messageArrayToMsgElements(chatType, uniseq.qqMsgId, peerId, message).also { - if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。") - }.second.filter { - it.elementType != -1 - } as ArrayList - - // ActionMsg No Care - if (msg.isEmpty()) { - return Result.success(uniseq.copy(msgTime = System.currentTimeMillis())) - } - - val totalSize = msg.filter { - it.elementType == MsgConstant.KELEMTYPEPIC || - it.elementType == MsgConstant.KELEMTYPEPTT || - it.elementType == MsgConstant.KELEMTYPEVIDEO - }.map { - (it.picElement?.fileSize ?: 0) + (it.pttElement?.fileSize - ?: 0) + (it.videoElement?.fileSize ?: 0) - }.reduceOrNull { a, b -> a + b } ?: 0 - val estimateTime = (totalSize / (300 * 1024)) * 1000 + 2000 - - lateinit var sendResult: SendMsgResult // msgTime to msgHash - val sendRet = withTimeoutOrNull>(estimateTime) { - suspendCancellableCoroutine { - GlobalScope.launch { - sendResult = sendMessageWithoutMsgId(chatType, peerId, msg, fromId) { code, message -> - callback.onResult(code, message) - it.resume(code to message) - } - } - } - } - - if (sendRet?.first != 0) { - //return Result.failure(SendMsgException(sendRet?.second ?: "发送消息超时")) - return Result.success(uniseq.copy(isTimeout = true)) - } - return Result.success(sendResult) - } - - suspend fun sendMessageWithoutMsgId( - chatType: Int, - peerId: String, - message: ArrayList, - fromId: String = peerId, - callback: IOperateCallback - ): SendMsgResult { - return sendMessageWithoutMsgId(generateContact(chatType, peerId, fromId), message, callback) - } - - fun sendMessageWithoutMsgId( - contact: Contact, - message: ArrayList, - callback: IOperateCallback - ): SendMsgResult { - val uniseq = generateMsgId(contact.chatType) - val nonMsg: Boolean = message.isEmpty() - return if (!nonMsg) { - val service = QRoute.api(IMsgService::class.java) - if (callback is MsgSvc.MessageCallback) { - callback.msgHash = uniseq.msgHashId - } - - service.sendMsg( - contact, - uniseq.qqMsgId, - message, - callback - ) - - uniseq.copy(msgTime = System.currentTimeMillis()) - } else { - uniseq.copy(msgHashId = 0, msgTime = System.currentTimeMillis()) - } - } - - suspend fun sendMessageWithMsgId( - chatType: Int, - peerId: String, - message: JsonArray, - callback: IOperateCallback, - fromId: String = peerId - ): SendMsgResult { - val uniseq = generateMsgId(chatType) - val msg = messageArrayToMsgElements(chatType, uniseq.qqMsgId, peerId, message).also { - if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。") - }.second.filter { - it.elementType != -1 - } as ArrayList - val contact = generateContact(chatType, peerId, fromId) - val nonMsg: Boolean = message.isEmpty() - return if (!nonMsg) { - val service = QRoute.api(IMsgService::class.java) - if (callback is MsgSvc.MessageCallback) { - callback.msgHash = uniseq.msgHashId - } - - service.sendMsg( - contact, - uniseq.qqMsgId, - msg, - callback - ) - uniseq.copy(msgTime = System.currentTimeMillis()) - } else { - uniseq.copy(msgHashId = 0, msgTime = System.currentTimeMillis()) - } - } - - fun sendMessageWithMsgId( - contact: Contact, - message: ArrayList, - uniseq: Long, - callback: IOperateCallback - ): SendMsgResult { - val nonMsg: Boolean = message.isEmpty() - if (!nonMsg) { - val service = QRoute.api(IMsgService::class.java) - service.sendMsg( - contact, - uniseq, - message, - callback - ) - } - return SendMsgResult( - msgTime = if (nonMsg) 0 else System.currentTimeMillis(), - msgHashId = 0, - qqMsgId = uniseq - ) - } - - suspend fun sendMessageNoCb( - chatType: Int, - peerId: String, - message: JsonArray, - fromId: String = peerId - ): SendMsgResult { - val uniseq = generateMsgId(chatType) - val msg = messageArrayToMsgElements(chatType, uniseq.qqMsgId, peerId, message).also { - if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。") - }.second.filter { - it.elementType != -1 - } as ArrayList - val contact = generateContact(chatType, peerId, fromId) - return if (!message.isEmpty()) { - val service = QRoute.api(IMsgService::class.java) - return suspendCancellableCoroutine { - service.sendMsg(contact, uniseq.qqMsgId, msg) { _, _ -> - it.resume(uniseq.copy(msgTime = System.currentTimeMillis())) - } - } - } else { - uniseq.copy(msgHashId = 0, msgTime = 0) - } - } - - suspend fun generateContact(chatType: Int, id: String, subId: String = ""): Contact { - val peerId = when (chatType) { - MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> { - ContactHelper.getUidByUinAsync(id.toLong()) - } - - else -> id - } - return if (chatType == MsgConstant.KCHATTYPEGUILD) { - Contact(chatType, subId, peerId) - } else { - Contact(chatType, peerId, subId) - } - } - - fun obtainMessageTypeByDetailType(detailType: String): Int { - return when (detailType) { - "troop", "group" -> MsgConstant.KCHATTYPEGROUP - "private" -> MsgConstant.KCHATTYPEC2C - "less" -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN - "guild" -> MsgConstant.KCHATTYPEGUILD - else -> error("不支持的消息来源类型") - } - } - - fun obtainDetailTypeByMsgType(msgType: Int): String { - return when (msgType) { - MsgConstant.KCHATTYPEGROUP -> "group" - MsgConstant.KCHATTYPEC2C -> "private" - MsgConstant.KCHATTYPEGUILD -> "guild" - MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN -> "less" - else -> error("不支持的消息来源类型") - } - } - - suspend fun messageArrayToMsgElements( - chatType: Int, - msgId: Long, - peerId: String, - messageList: JsonArray - ): Pair> { - val msgList = arrayListOf() - var hasActionMsg = false - messageList.forEach { - val msg = it.jsonObject - val maker = NtMsgElementMaker[msg["type"].asString] - if (maker != null) { - try { - val data = msg["data"].asJsonObjectOrNull ?: EmptyJsonObject - maker(chatType, msgId, peerId, data).onSuccess { msgElem -> - msgList.add(msgElem) - }.onFailure { - if (it.javaClass != ActionMsgException::class.java) { - throw it - } else { - hasActionMsg = true - } - } - } catch (e: Throwable) { - LogCenter.log(e.stackTraceToString(), Level.ERROR) - } - } else { - LogCenter.log("不支持的消息类型: ${msg["type"].asString}", Level.ERROR) - return false to arrayListOf() - } - } - return hasActionMsg to msgList - } - - suspend fun messageArrayToRichText( - chatType: Int, - msgId: Long, - peerId: String, - messageList: JsonArray - ): Result> { - val elemMaker = ElemMaker() - messageList.forEach { element -> - val msg = element.asJsonObject - val maker = ElemMaker[msg["type"].asString] - if (maker != null) { - try { - val data = msg["data"].asJsonObjectOrNull ?: EmptyJsonObject - maker(elemMaker, chatType, msgId, peerId, data) - } catch (e: Throwable) { - if (e.javaClass != ActionMsgException::class.java) { - LogCenter.log(e.stackTraceToString(), Level.ERROR) - } - } - } else { - LogCenter.log("不支持的消息类型: ${msg["type"].asString}", Level.ERROR) - } - } - return Result.success(elemMaker.getDesc() to elemMaker.getRich()) - } - - fun generateMsgIdHash(chatType: Int, msgId: Long): Int { - val key = when (chatType) { - MsgConstant.KCHATTYPEGROUP -> "grp$msgId" - MsgConstant.KCHATTYPEC2C -> "c2c$msgId" - MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> "tmpgrp$msgId" - MsgConstant.KCHATTYPEGUILD -> "guild$msgId" - else -> error("不支持的消息来源类型 | generateMsgIdHash: $chatType") - } - return abs(key.hashCode()) - } - - fun generateMsgId(chatType: Int): SendMsgResult { - val msgId = createMessageUniseq(chatType, System.currentTimeMillis()) - val hashCode: Int = generateMsgIdHash(chatType, msgId) - return SendMsgResult(hashCode, msgId, 0) - } - - fun getMsgMappingByHash(hash: Int): MessageMapping? { - val db = MessageDB.getInstance() - return db.messageMappingDao().queryByMsgHashId(hash) - } - - fun getMsgMappingBySeq(chatType: Int, peerId: String, msgSeq: Int): MessageMapping? { - val db = MessageDB.getInstance() - return db.messageMappingDao().queryByMsgSeq(chatType, peerId, msgSeq) - } - - fun removeMsgByHashCode(hashCode: Int) { - MessageDB.getInstance() - .messageMappingDao() - .deleteByMsgHash(hashCode) - } - - fun saveMsgMapping( - hash: Int, - qqMsgId: Long, - time: Long, - chatType: Int, - peerId: String, - subPeerId: String, - msgSeq: Int, - subChatType: Int = chatType - ) { - val database = MessageDB.getInstance() - val mapping = MessageMapping(hash, qqMsgId, chatType, subChatType, peerId, time, msgSeq, subPeerId) - database.messageMappingDao().insert(mapping) - } - - fun saveMsgMappingNotExist( - hash: Int, - qqMsgId: Long, - time: Long, - chatType: Int, - peerId: String, - subPeerId: String, - msgSeq: Int, - subChatType: Int = chatType - ) { - val database = MessageDB.getInstance() - val mapping = MessageMapping(hash, qqMsgId, chatType, subChatType, peerId, time, msgSeq, subPeerId) - database.messageMappingDao().insertNotExist(mapping) - } - - external fun createMessageUniseq(chatType: Int, time: Long): Long - - fun decodeCQCode(code: String): JsonArray { - val arrayList = ArrayList() - val msgList = nativeDecodeCQCode(code) - msgList.forEach { - val params = hashMapOf() - it.forEach { (key, value) -> - if (key != "_type") { - params[key] = value.json - } - } - val data = mapOf( - "type" to it["_type"]!!.json, - "data" to JsonObject(params) - ) - arrayList.add(data.json) - } - return arrayList.jsonArray - } - - private external fun nativeDecodeCQCode(code: String): List> - external fun nativeEncodeCQCode(segment: List>): String -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/MusicHelper.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/MusicHelper.kt deleted file mode 100644 index f221723d..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/MusicHelper.kt +++ /dev/null @@ -1,97 +0,0 @@ -package moe.fuqiuluo.shamrock.helper - -import io.ktor.client.request.get -import io.ktor.client.statement.bodyAsText -import kotlinx.serialization.json.Json -import moe.fuqiuluo.qqinterface.servlet.ark.data.ArkAppInfo -import moe.fuqiuluo.qqinterface.servlet.ark.ArkMsgSvc -import moe.fuqiuluo.shamrock.tools.GlobalClient -import moe.fuqiuluo.shamrock.tools.asInt -import moe.fuqiuluo.shamrock.tools.asJsonArray -import moe.fuqiuluo.shamrock.tools.asJsonArrayOrNull -import moe.fuqiuluo.shamrock.tools.asJsonObject -import moe.fuqiuluo.shamrock.tools.asString -import moe.fuqiuluo.shamrock.tools.asStringOrNull -import moe.fuqiuluo.shamrock.utils.MD5 - -internal object MusicHelper { - suspend fun tryShare163MusicById(chatType: Int, peerId: Long, msgId: Long, id: String): Boolean { - try { - val respond = GlobalClient.get("https://music.163.com/api/song/detail/?id=$id&ids=[$id]") - val songInfo = Json.parseToJsonElement(respond.bodyAsText()).asJsonObject["songs"].asJsonArray.first().asJsonObject - val name = songInfo["name"].asString - val title = songInfo["name"].asString - val singerName = songInfo["artists"].asJsonArray.first().asJsonObject["name"].asString - val previewUrl = songInfo["album"].asJsonObject["picUrl"].asString - val playUrl = "https://music.163.com/song/media/outer/url?id=$id.mp3" - val jumpUrl = "https://music.163.com/#/song?id=$id" - ArkMsgSvc.tryShareMusic( - chatType, - peerId, - msgId, - ArkAppInfo.NetEaseMusic, - title.ifBlank { name }, - singerName, - jumpUrl, - previewUrl, - playUrl - ) - return true - } catch (e: Throwable) { - LogCenter.log(e.stackTraceToString(), Level.ERROR) - } - return false - } - - suspend fun tryShareQQMusicById(chatType: Int, peerId: Long, msgId: Long, id: String): Boolean { - try { - val respond = GlobalClient.get("https://u.y.qq.com/cgi-bin/musicu.fcg?format=json&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq.json&needNewCode=0&data={%22comm%22:{%22ct%22:24,%22cv%22:0},%22songinfo%22:{%22method%22:%22get_song_detail_yqq%22,%22param%22:{%22song_type%22:0,%22song_mid%22:%22%22,%22song_id%22:$id},%22module%22:%22music.pf_song_detail_svr%22}}") - val songInfo = Json.parseToJsonElement(respond.bodyAsText()).asJsonObject["songinfo"].asJsonObject - if (songInfo["code"].asInt != 0) { - LogCenter.log("获取QQ音乐($id)的歌曲信息失败。") - return false - } else { - val data = songInfo["data"].asJsonObject - val trackInfo = data["track_info"].asJsonObject - val mid = trackInfo["mid"].asString - val previewMid = trackInfo["album"].asJsonObject["mid"].asString - val singerMid = trackInfo["singer"].asJsonArrayOrNull?.let { - it[0].asJsonObject["mid"].asStringOrNull - } ?: "" - val name = trackInfo["name"].asString - val title = trackInfo["title"].asString - val singerName = trackInfo["singer"].asJsonArray.first().asJsonObject["name"].asString - val vs = trackInfo["vs"].asJsonArrayOrNull?.let { - it[0].asStringOrNull - } ?: "" - val code = MD5.getMd5Hex("${mid}q;z(&l~sdf2!nK".toByteArray()).substring(0 .. 4).uppercase() - val playUrl = "http://c6.y.qq.com/rsc/fcgi-bin/fcg_pyq_play.fcg?songid=&songmid=$mid&songtype=1&fromtag=50&uin=&code=$code" - val previewUrl = if (vs.isNotEmpty()) { - "http://y.gtimg.cn/music/photo_new/T062R150x150M000$vs}.jpg" - } else if (previewMid.isNotEmpty()) { - "http://y.gtimg.cn/music/photo_new/T002R150x150M000$previewMid.jpg" - } else if (singerMid.isNotEmpty()){ - "http://y.gtimg.cn/music/photo_new/T001R150x150M000$singerMid.jpg" - } else { - "" - } - val jumpUrl = "https://i.y.qq.com/v8/playsong.html?platform=11&appshare=android_qq&appversion=10030010&hosteuin=oKnlNenz7i-s7c**&songmid=${mid}&type=0&appsongtype=1&_wv=1&source=qq&ADTAG=qfshare" - ArkMsgSvc.tryShareMusic( - chatType, - peerId, - msgId, - ArkAppInfo.QQMusic, - title.ifBlank { name }, - singerName, - jumpUrl, - previewUrl, - playUrl - ) - return true - } - } catch (e: Throwable) { - LogCenter.log(e.stackTraceToString(), Level.ERROR) - } - return false - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/HTTPServer.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/HTTPServer.kt deleted file mode 100644 index 59ff3363..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/HTTPServer.kt +++ /dev/null @@ -1,174 +0,0 @@ -package moe.fuqiuluo.shamrock.remote - -import io.ktor.server.application.Application -import io.ktor.server.application.install -import io.ktor.server.engine.ApplicationEngine -import io.ktor.server.engine.ApplicationEngineEnvironmentBuilder -import io.ktor.server.engine.applicationEngineEnvironment -import io.ktor.server.engine.connector -import io.ktor.server.engine.embeddedServer -import io.ktor.server.engine.sslConnector -import io.ktor.server.netty.Netty -import io.ktor.server.routing.routing -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import moe.fuqiuluo.shamrock.remote.api.* -import moe.fuqiuluo.shamrock.remote.config.* -import moe.fuqiuluo.shamrock.remote.plugin.Auth -import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.xposed.helper.internal.DataRequester -import moe.fuqiuluo.shamrock.xposed.loader.NativeLoader -import org.slf4j.LoggerFactory -import java.security.KeyStore - - -internal object HTTPServer { - @JvmStatic - var isServiceStarted = false - internal var startTime = 0L - - private val actionMutex = Mutex() - private lateinit var server: ApplicationEngine - internal var currServerPort: Int = 0 - - private fun Application.configModule() { - install(Auth).let { - val token = ShamrockConfig.getToken() - if (token.isBlank()) { - LogCenter.log("未配置Token,将不进行鉴权。", Level.WARN) - } else { - LogCenter.log("配置Token: $token", Level.INFO) - } - } - contentNegotiation() - statusPages() - routing { - echoVersion() - obtainFrameworkInfo() - registerBDH() - userAction() - messageAction() - troopAction() - friendAction() - ticketActions() - fetchRes() - showLog() - profileRouter() - weatherAction() - otherAction() - guildAction() - testAction() - requestRouter() - fav() - if (ShamrockConfig.isDev()) { - qsign() - obtainProtocolData() - } - } - -// intercept(ApplicationCallPipeline.Plugins) { -// call.response.headers.appendIfAbsent("Content-Type", ContentType.Application.Json.toString()) -// } - } - - private fun ApplicationEngineEnvironmentBuilder.configSSL() { - try { - val keyStoreFile = ShamrockConfig.getKeyStorePath() - val pwd = ShamrockConfig.sslPwd().also { - if (it == null || it.isEmpty()) { - LogCenter.log("SSL 密码未填写。", Level.ERROR) - return - } - } - val privatePwd = ShamrockConfig.sslPrivatePwd().also { - if (it.isNullOrEmpty()) { - LogCenter.log("SSL Private密码未填写。", Level.ERROR) - return - } - } - val keyAlias = ShamrockConfig.sslAlias().also { - if (it.isNullOrEmpty()) { - LogCenter.log("SSL Alias未填写。", Level.ERROR) - return - } - } - - val keyStore = KeyStore.getInstance("BKS") - keyStore.load(null, pwd) - - keyStoreFile?.inputStream().use { - keyStore.load(it, pwd) - } - - sslConnector( - keyStore = keyStore, - keyAlias = keyAlias!!, - keyStorePassword = { pwd!! }, - privateKeyPassword = { privatePwd!!.toCharArray() }) { - this.port = ShamrockConfig.getSslPort() - this.keyStorePath = keyStoreFile - LogCenter.log("SSL 配置成功,端口: ${this.port}", Level.INFO) - } - } catch (e: Exception) { - LogCenter.log("SSL 配置错误: ${e.stackTraceToString()}", Level.ERROR) - } - } - - suspend fun start(port: Int) { - if (isServiceStarted) return - actionMutex.withLock { - val environment = applicationEngineEnvironment { - log = LoggerFactory.getLogger("ktor.application") - connector { - this.port = port - } - if (ShamrockConfig.ssl()) - configSSL() - module { configModule() } - } - server = embeddedServer(Netty, environment) - server.start(wait = false) - } - startTime = System.currentTimeMillis() - isServiceStarted = true - currServerPort = port - LogCenter.log("Start HTTP Server: http://0.0.0.0:$currServerPort/") - DataRequester.request("success", values = mapOf( - "port" to currServerPort, - "voice" to NativeLoader.isVoiceLoaded - )) - } - - fun isActive(): Boolean { - return server.application.isActive - } - - fun changePort(port: Int) { - if (currServerPort == port && isServiceStarted) return - GlobalScope.launch { - stop() - start(port) - } - } - - suspend fun stop() { - actionMutex.withLock { - server.stop() - isServiceStarted = false - } - } - - fun restart() { - if(!isServiceStarted) return - val post = currServerPort - GlobalScope.launch { - stop() - start(post) - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/ActionManager.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/ActionManager.kt deleted file mode 100644 index 62d5ddc3..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/ActionManager.kt +++ /dev/null @@ -1,177 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action - -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import moe.fuqiuluo.shamrock.remote.structures.EmptyObject -import moe.fuqiuluo.shamrock.remote.structures.Status -import moe.fuqiuluo.shamrock.remote.structures.resultToString -import moe.fuqiuluo.shamrock.tools.* -import moe.fuqiuluo.shamrock.tools.json - -internal object ActionManager { - val actionMap = mutableMapOf() - - init { - initManager() - } - - operator fun get(action: String): IActionHandler? { - return actionMap[action] - } -} - -internal abstract class IActionHandler { - protected abstract suspend fun internalHandle(session: ActionSession): String - - open val requiredParams: Array = arrayOf() - - suspend fun handle(session: ActionSession): String { - requiredParams.forEach { - if (!session.has(it)) { - return noParam(it, session.echo) - } - } - return internalHandle(session) - } - - protected fun ok( - msg: String = "", - echo: JsonElement - ): String { - return resultToString(true, Status.Ok, EmptyObject, msg, echo = echo) - } - - protected inline fun ok(data: T, echo: JsonElement, msg: String = ""): String { - return resultToString(true, Status.Ok, data!!, msg, echo = echo) - } - - protected fun noParam(paramName: String, echo: JsonElement): String { - return failed(Status.BadParam, "lack of [$paramName]", echo) - } - - protected fun badParam(why: String, echo: JsonElement): String { - return failed(Status.BadParam, why, echo) - } - - protected fun error(why: String, echo: JsonElement, arrayResult: Boolean = false): String { - return failed(Status.InternalHandlerError, why, echo, arrayResult) - } - - protected fun logic(why: String, echo: JsonElement, arraayResult: Boolean = false): String { - return failed(Status.LogicError, why, echo, arraayResult) - } - - protected fun failed(status: Status, msg: String, echo: JsonElement, arrResult: Boolean = false): String { - return resultToString(false, status, if (arrResult) EmptyJsonArray else EmptyJsonObject, msg, echo = echo) - } -} - -internal class ActionSession { - private val params: JsonObject - internal val echo: JsonElement - - constructor( - values: Map, - echo: JsonElement = EmptyJsonString - ) { - val map = hashMapOf() - values.forEach { (key, value) -> - if (value != null) { - when (value) { - is String -> map[key] = value.json - is Number -> map[key] = value.json - is Char -> map[key] = JsonPrimitive(value.code.toByte()) - is Boolean -> map[key] = value.json - is JsonObject -> map[key] = value - is JsonArray -> map[key] = value - else -> error("unsupported type: ${value::class.java}") - } - } - } - this.echo = echo - this.params = JsonObject(map) - } - - constructor( - params: JsonObject, - echo: JsonElement - ) { - this.echo = echo - this.params = params - } - - fun getLong(key: String): Long { - return params[key].asLong - } - - fun getLongOrNull(key: String): Long? { - return params[key].asLongOrNull - } - - fun getInt(key: String): Int { - return params[key].asInt - } - - fun getIntOrNull(key: String): Int? { - return params[key].asIntOrNull - } - - fun isString(key: String): Boolean { - val element = params[key] - return element is JsonPrimitive && element.isString - } - - fun isArray(key: String): Boolean { - val element = params[key] - return element is JsonArray - } - - fun isObject(key: String): Boolean { - val element = params[key] - return element is JsonObject - } - - fun getString(key: String): String { - return params[key].asString - } - - fun getStringOrNull(key: String): String? { - return params[key].asStringOrNull - } - - fun getBoolean(key: String): Boolean { - return params[key].asBoolean - } - - fun getBooleanOrDefault(key: String, default: Boolean? = null): Boolean { - return params[key].asBooleanOrNull ?: default as Boolean - } - - fun getJsonElement(key: String): JsonElement { - return params[key]!! - } - - fun getJsonElementOrNull(key: String): JsonElement? { - return params[key] - } - - fun getObject(key: String): JsonObject { - return params[key].asJsonObject - } - - fun getObjectOrNull(key: String): JsonObject? { - return params[key].asJsonObjectOrNull - } - - fun getArray(key: String): JsonArray { - return params[key].asJsonArray - } - - fun getArrayOrNull(key: String): JsonArray? { - return params[key].asJsonArrayOrNull - } - - fun has(key: String) = params.containsKey(key) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/AdaptShareJson.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/AdaptShareJson.kt deleted file mode 100644 index b3dfe3e2..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/AdaptShareJson.kt +++ /dev/null @@ -1,44 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.ark.LightAppSvc -import moe.fuqiuluo.qqinterface.servlet.ark.data.ArkAppInfo -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("adapt_share_json") -internal object AdaptShareJson: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - //val json = if(session.isString("json")) session.getString("json") - //else session.getJsonElement("json").toString() - val cover = session.getString("cover") - val desc = session.getString("desc") - val url = session.getStringOrNull("url") ?: "" - return invoke(cover, desc, url, session.echo) - } - - suspend operator fun invoke(cover: String, desc: String, url: String, echo: JsonElement = EmptyJsonString): String { - /* - ArkMsgSvc.tryShareJsonMessage(json).onSuccess { - return ok(SignArkMessageResult(it), echo = echo) - }.onFailure { - return error(it.message ?: it.toString(), echo) - }*/ - LightAppSvc.adaptShareJumpUrl(ArkAppInfo.DanMaKu, cover, desc, url).onSuccess { - return ok(AdaptShareInfo(it), echo = echo) - }.onFailure { - return error(it.message ?: it.toString(), echo) - } - return logic("logic error", echo) - } - - override val requiredParams: Array = arrayOf("cover", "desc") - - @Serializable - data class AdaptShareInfo( - val result: String - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/BanTroopMember.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/BanTroopMember.kt deleted file mode 100644 index f2b45a4a..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/BanTroopMember.kt +++ /dev/null @@ -1,34 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("set_group_ban") -internal object BanTroopMember: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - val userId = session.getLong("user_id") - val duration = session.getIntOrNull("duration") ?: (30 * 60) - - return invoke(groupId, userId, duration, session.echo) - } - - operator fun invoke( - groupId: Long, - userId: Long, - duration: Int = 30 * 60, - echo: JsonElement = EmptyJsonString - ): String { - if (!GroupSvc.isAdmin(groupId)) { - return logic("You are not the administrator of the group.", echo) - } - GroupSvc.banMember(groupId, userId, duration) - return ok("成功", echo) - } - - override val requiredParams: Array = arrayOf("group_id", "user_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/CleanCache.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/CleanCache.kt deleted file mode 100644 index 8fca2fa3..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/CleanCache.kt +++ /dev/null @@ -1,29 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.shamrock.utils.FileUtils -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.utils.MMKVFetcher -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("clean_cache") -internal object CleanCache: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - return invoke(session.echo) - } - - operator fun invoke(echo: JsonElement = EmptyJsonString): String { - FileUtils.clearCache() - MMKVFetcher.mmkvWithId("hash2id") - .clear() - MMKVFetcher.mmkvWithId("id2id") - .clear() - MMKVFetcher.mmkvWithId("seq2id") - .clear() - MMKVFetcher.mmkvWithId("audio2silk") - .clear() - return ok("成功", echo) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/ClearMsgs.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/ClearMsgs.kt deleted file mode 100644 index abebb2c7..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/ClearMsgs.kt +++ /dev/null @@ -1,32 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("clear_msgs", ["clear_messages"]) -internal object ClearMsgs: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val msgType = session.getString("message_type") - val peerId = session.getString(if (msgType == "group") "group_id" else "user_id") - return invoke(msgType, peerId, session.echo) - } - - suspend operator fun invoke( - msgType: String, - peerId: String, - echo: JsonElement = EmptyJsonString - ): String { - val chatType = MessageHelper.obtainMessageTypeByDetailType(msgType) - val contact = MessageHelper.generateContact(chatType, peerId, "") - NTServiceFetcher.kernelService.wrapperSession.msgService.clearMsgRecords(contact, null) - return ok(echo = echo) - } - - override val requiredParams: Array - get() = arrayOf("message_type") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/CreateGroupFileFolder.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/CreateGroupFileFolder.kt deleted file mode 100644 index 167ab11b..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/CreateGroupFileFolder.kt +++ /dev/null @@ -1,28 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.FileSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("create_group_file_folder") -internal object CreateGroupFileFolder: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - val folderName = session.getString("name") - val echo = session.echo - return invoke(groupId, folderName, echo) - } - - suspend operator fun invoke(groupId: Long, folderName: String, echo: JsonElement = EmptyJsonString): String { - val result = FileSvc.createFileFolder(groupId, folderName) - if (result.isFailure) { - return ok(msg = result.exceptionOrNull()?.message ?: "无法创建群文件夹", echo = echo) - } - return ok(data = result.getOrThrow(), msg = "成功", echo = echo) - } - - override val requiredParams: Array = arrayOf("group_id", "name") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/CreateGuildRole.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/CreateGuildRole.kt deleted file mode 100644 index 981fe19e..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/CreateGuildRole.kt +++ /dev/null @@ -1,42 +0,0 @@ -@file:Suppress("UNCHECKED_CAST") - -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GProSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.tools.asString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("create_guild_role") -internal object CreateGuildRole: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val guildId = session.getString("guild_id").toULong() - val name = session.getString("name") - val color = session.getLong("color") - val initialUsers = session.getArray("initial_users").map { - it.asString.toULong() - } - return invoke(guildId, color, name, initialUsers, session.echo) - } - - suspend operator fun invoke(guildId: ULong, color: Long, name: String, initialUsers: List, echo: JsonElement = EmptyJsonString): String { - val result = GProSvc.createGuildRole(guildId, name, color, initialUsers as ArrayList).onFailure { - return error(it.message ?: "Unknown error", echo) - }.getOrThrow() - return ok(data = CreateGuildRoleResult( - result.roleId.toULong() - ), echo = echo) - } - - override val requiredParams: Array = arrayOf("guild_id", "color", "name", "initial_users") - - @Serializable - data class CreateGuildRoleResult( - @SerialName("role_id") val roleId: ULong - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/DeleteEssenceMessage.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/DeleteEssenceMessage.kt deleted file mode 100644 index 89cd482a..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/DeleteEssenceMessage.kt +++ /dev/null @@ -1,34 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.qqinterface.servlet.MsgSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("delete_essence_msg", ["delete_essence_message"]) -internal object DeleteEssenceMessage: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val messageId = session.getInt("message_id") - return invoke(messageId, session.echo) - } - - suspend operator fun invoke(messageId: Int, echo: JsonElement = EmptyJsonString): String { - val msg = MsgSvc.getMsg(messageId).onFailure { - return logic("Obtain msg failed, please check your msg_id.", echo) - }.getOrThrow() - val (success, tip) = GroupSvc.deleteEssenceMessage( - if (msg.chatType == MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0, - msg.msgSeq, - msg.msgRandom - ) - return if (success) { - ok("成功", echo) - } else { - logic(tip, echo) - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/DeleteGroupFile.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/DeleteGroupFile.kt deleted file mode 100644 index 58eec212..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/DeleteGroupFile.kt +++ /dev/null @@ -1,27 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.FileSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("delete_group_file") -internal object DeleteGroupFile: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - val fileId = session.getString("file_id") - val busid = session.getInt("busid") - return invoke(groupId, fileId, busid, session.echo) - } - - suspend operator fun invoke(groupId: Long, fileId: String, bizId: Int, echo: JsonElement = EmptyJsonString): String { - if(!FileSvc.deleteGroupFile(groupId, bizId, fileId)) { - return error("删除失败", echo = echo) - } - return ok("成功", echo) - } - - override val requiredParams: Array = arrayOf("group_id", "file_id", "busid") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/DeleteGroupFolder.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/DeleteGroupFolder.kt deleted file mode 100644 index 300732d2..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/DeleteGroupFolder.kt +++ /dev/null @@ -1,26 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.FileSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("delete_group_folder") -internal object DeleteGroupFolder: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - val folderId = session.getString("folder_id") - return invoke(groupId, folderId, session.echo) - } - - suspend operator fun invoke(groupId: Long, folderId: String, echo: JsonElement = EmptyJsonString): String { - if(!FileSvc.deleteGroupFolder(groupId, folderId)) { - return error(why = "删除群文件夹失败", echo = echo) - } - return ok(msg = "成功", echo = echo) - } - - override val requiredParams: Array = arrayOf("group_id", "folder_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/DeleteGuildRole.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/DeleteGuildRole.kt deleted file mode 100644 index 754368c2..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/DeleteGuildRole.kt +++ /dev/null @@ -1,24 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GProSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("delete_guild_role") -internal object DeleteGuildRole: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val guildId = session.getString("guild_id").toULong() - val roleId = session.getString("role_id").toULong() - return invoke(guildId, roleId, session.echo) - } - - operator fun invoke(guildId: ULong, roleId: ULong, echo: JsonElement = EmptyJsonString): String { - GProSvc.deleteGuildRole(guildId, roleId) - return ok("success", echo = echo) - } - - override val requiredParams: Array = arrayOf("guild_id", "role_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/DeleteMessage.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/DeleteMessage.kt deleted file mode 100644 index 646d2022..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/DeleteMessage.kt +++ /dev/null @@ -1,23 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.MsgSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("delete_message", ["delete_msg"]) -internal object DeleteMessage: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val hashCode = session.getString("message_id").toInt() - return invoke(hashCode, session.echo) - } - - suspend operator fun invoke(msgHash: Int, echo: JsonElement = EmptyJsonString): String { - MsgSvc.recallMsg(msgHash) - return ok("成功", echo) - } - - override val requiredParams: Array = arrayOf("message_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/DownloadFile.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/DownloadFile.kt deleted file mode 100644 index 17cc0963..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/DownloadFile.kt +++ /dev/null @@ -1,140 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import android.util.Base64 -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonObject -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.tools.asString -import moe.fuqiuluo.shamrock.utils.DownloadUtils -import moe.fuqiuluo.shamrock.utils.FileUtils -import moe.fuqiuluo.shamrock.utils.MD5 -import moe.fuqiuluo.symbols.OneBotHandler -import java.io.File - -@OneBotHandler("download_file") -internal object DownloadFile: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val url = session.getStringOrNull("url") - val name = session.getStringOrNull("name") - val b64 = session.getStringOrNull("base64") - val rootDir = session.getStringOrNull("root") - val threadCnt = session.getIntOrNull("thread_cnt") ?: 3 - val headers = if (session.has("headers")) (if (session.isArray("headers")) { - session.getArray("headers").map { - it.asString - } - } else { - session.getString("headers").split("\r\n") - }) else emptyList() - return invoke(url, b64, threadCnt, headers, name, rootDir, session.echo) - } - - suspend operator fun invoke( - url: String?, - base64: String?, - threadCnt: Int, - headers: List, - name: String?, - root: String?, - echo: JsonElement = EmptyJsonString - ): String { - if (url != null) { - val headerMap = mutableMapOf( - "User-Agent" to "Shamrock" - ) - headers.forEach { - val pair = it.split("=") - if (pair.size >= 2) { - val (k, v) = pair - headerMap[k] = v - } - } - return invoke(url, threadCnt, headerMap, name, root, echo) - } else if (base64 != null) { - return invoke(base64, name, root, echo) - } else { - return noParam("url/base64", echo) - } - } - - operator fun invoke( - base64: String, - name: String?, - root: String?, - echo: JsonElement - ): String { - kotlin.runCatching { - val bytes = Base64.decode(base64, Base64.DEFAULT) - FileUtils.getTmpFile("cache").also { - it.writeBytes(bytes) - } - }.onSuccess { - var tmp = if (name == null) - FileUtils.renameByMd5(it) - else it.parentFile!!.resolve(name).also { target -> - it.renameTo(target) - it.delete() - } - if (root != null) { - tmp = File(root).resolve(name ?: tmp.name).also { - tmp.renameTo(it) - } - } - return ok(data = DownloadResult( - file = tmp.absolutePath, - md5 = MD5.genFileMd5Hex(tmp.absolutePath) - ), msg = "成功", echo = echo) - }.onFailure { - return logic("Base64格式错误", echo) - } - return logic("未知错误", echo) - } - - suspend operator fun invoke( - url: String, - threadCnt: Int, - headers: Map, - name: String?, - root: String?, - echo: JsonElement = EmptyJsonString - ): String { - return kotlin.runCatching { - var tmp = FileUtils.getTmpFile("cache") - if(!DownloadUtils.download( - urlAdr = url, - dest = tmp, - headers = headers, - threadCount = threadCnt - )) { - return error("下载失败 (0x1)", echo) - } - tmp = if (name == null) { - FileUtils.renameByMd5(tmp) - } else { - val newFile = tmp.parentFile!!.resolve(name) - tmp.renameTo(newFile) - newFile - } - if (root != null) { - tmp = File(root).resolve(name ?: tmp.name).also { - tmp.renameTo(it) - } - } - ok(data = DownloadResult( - file = tmp.absolutePath, - md5 = MD5.genFileMd5Hex(tmp.absolutePath) - ), msg = "成功", echo = echo) - }.getOrElse { - logic(it.stackTraceToString(), echo) - } - } - - @Serializable - data class DownloadResult( - val file: String, - val md5: String - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavAddImageMsg.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavAddImageMsg.kt deleted file mode 100644 index 9f3a23dc..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavAddImageMsg.kt +++ /dev/null @@ -1,159 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import android.graphics.BitmapFactory -import kotlinx.io.core.ByteReadPacket -import kotlinx.io.core.discardExact -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.decodeFromByteArray -import kotlinx.serialization.json.JsonElement - -import moe.fuqiuluo.qqinterface.servlet.QFavSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.utils.CryptTools -import moe.fuqiuluo.shamrock.utils.DeflateTools -import moe.fuqiuluo.shamrock.utils.FileUtils -import moe.fuqiuluo.symbols.OneBotHandler -import moe.fuqiuluo.symbols.decodeProtobuf -import protobuf.fav.WeiyunComm - -@OneBotHandler("fav.add_image_msg", ["fav.add_image_message"]) -internal object FavAddImageMsg: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val uin = session.getLong("user_id") - val nickName = session.getString("nick") - val groupName = session.getStringOrNull("group_name") ?: "" - val groupId = session.getLongOrNull("group_id") ?: 0L - val file = session.getString("file") - return invoke(uin, nickName, file, groupName, groupId, session.echo) - } - - suspend operator fun invoke( - uin: Long, - nickName: String, - fileText: String, - groupName: String = "", - groupId: Long = 0L, - echo: JsonElement = EmptyJsonString - ): String { - val image = fileText.let { - val md5 = it.replace(regex = "[{}\\-]".toRegex(), replacement = "").split(".")[0].lowercase() - if (md5.length == 32) { - FileUtils.getFileByMd5(it) - } else { - FileUtils.parseAndSave(it) - } - } - - val options = BitmapFactory.Options() - BitmapFactory.decodeFile(image.absolutePath, options) - lateinit var picUrl: String - lateinit var picId: String - lateinit var itemId: String - lateinit var md5: String - - QFavSvc.applyUpImageMsg(uin, nickName, - image = image, - groupName = groupName, - groupId = groupId, - width = options.outWidth, - height = options.outHeight - ).onSuccess { - if (it.mHttpCode == 200 && it.mResult == 0) { - val readPacket = ByteReadPacket(DeflateTools.ungzip(it.mRespData)) - readPacket.discardExact(6) - val allLength = readPacket.readInt() - val dataLength = readPacket.readInt() - val headLength = allLength - dataLength - 16 - readPacket.discardExact(2) - ByteArray(headLength).also { - readPacket.readFully(it, 0, it.size) - } - val data = ByteArray(dataLength).also { - readPacket.readFully(it, 0, it.size) - } - - val resp = data.decodeProtobuf() - .resp!!.fastUploadResourceResp!!.picResultList!!.first() - val picInfo = resp.picInfo!! - picUrl = picInfo.uri - picId = picInfo.picId - md5 = picInfo.name - } else { - return logic(it.mErrDesc, echo) - } - }.onFailure { - return error(it.message ?: it.toString(), echo) - } - - val sha = CryptTools - .getSHA1("/storage/emulated/0/Android/data/com.tencent.mobileqq/Tencent/QQ_Collection/pic/" + md5.uppercase() + "_0") - - image.inputStream().use { - var offset = 0L - val block = ByteArray(131072) - var rest = image.length() - do { - val length = if (rest <= 131072) rest else 131072L - if(it.read(block, 0, length.toInt()) != -1) { - QFavSvc.sendPicUpBlock( - fileSize = image.length(), - offset = offset, - block = block, - blockSize = length, - pid = picId, - sha = sha - ).onFailure { - return error(it.message ?: it.toString(), echo) - } - offset += length - rest -= length - } else { - rest = -1 - } - } while (rest > 0) - } - - QFavSvc.addImageMsg( - uin, nickName, groupId, groupName, picUrl, picId, options.outWidth, options.outHeight, image.length(), md5.uppercase() - ).onFailure { - return error(it.message ?: it.toString(), echo) - }.onSuccess { - if (it.mHttpCode == 200 && it.mResult == 0) { - val readPacket = ByteReadPacket(DeflateTools.ungzip(it.mRespData)) - readPacket.discardExact(6) - val allLength = readPacket.readInt() - val dataLength = readPacket.readInt() - val headLength = allLength - dataLength - 16 - readPacket.discardExact(2) - ByteArray(headLength).also { - readPacket.readFully(it, 0, it.size) - } - val data = ByteArray(dataLength).also { - readPacket.readFully(it, 0, it.size) - } - val resp = data.decodeProtobuf().resp!! - itemId = resp.addRichMediaResp!!.cid - } - - System.gc() - } - - return ok(PicInfo( - picUrl = picUrl, - picId = picId, - id = itemId - ), echo) - } - - override val requiredParams: Array = arrayOf("user_id", "nick", "file") - - @Serializable - private data class PicInfo( - @SerialName("pic_url") val picUrl: String, - @SerialName("pic_id") val picId: String, - @SerialName("id") val id: String - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavAddTextMsg.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavAddTextMsg.kt deleted file mode 100644 index ae15c394..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavAddTextMsg.kt +++ /dev/null @@ -1,74 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.io.core.ByteReadPacket -import kotlinx.io.core.discardExact -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.decodeFromByteArray -import kotlinx.serialization.json.JsonElement - -import moe.fuqiuluo.qqinterface.servlet.QFavSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.utils.DeflateTools -import moe.fuqiuluo.symbols.OneBotHandler -import moe.fuqiuluo.symbols.decodeProtobuf -import protobuf.fav.WeiyunComm - -@OneBotHandler("fav.add_text_msg", ["fav.add_text_message"]) -internal object FavAddTextMsg: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val uin = session.getLong("user_id") - val nickName = session.getString("nick") - val groupName = session.getStringOrNull("group_name") ?: "" - val groupId = session.getLongOrNull("group_id") ?: 0L - val time = session.getLongOrNull("time") ?: System.currentTimeMillis() - val content = session.getString("content") - return invoke(uin, nickName, time, content, groupName, groupId, session.echo) - } - - suspend operator fun invoke( - uin: Long, - nickName: String, - time: Long = System.currentTimeMillis(), - content: String, - groupName: String = "", - groupId: Long = 0L, - echo: JsonElement = EmptyJsonString - ): String { - QFavSvc.addRichMediaMsg(uin, nickName, - time = time, - content = content, - groupName = groupName, - groupId = groupId - ).onSuccess { - return if (it.mHttpCode == 200 && it.mResult == 0) { - val readPacket = ByteReadPacket(DeflateTools.ungzip(it.mRespData)) - readPacket.discardExact(6) - val allLength = readPacket.readInt() - val dataLength = readPacket.readInt() - val headLength = allLength - dataLength - 16 - readPacket.discardExact(2) - ByteArray(headLength).also { - readPacket.readFully(it, 0, it.size) - } - val data = ByteArray(dataLength).also { - readPacket.readFully(it, 0, it.size) - } - val resp = data.decodeProtobuf().resp!!.addRichMediaResp!! - ok(data = QFavItem(resp.cid), echo) - } else { - logic(it.mErrDesc, echo) - } - } - return ok("请求已提交", echo) - } - - override val requiredParams: Array = arrayOf("user_id", "nick", "content") - - @Serializable - private data class QFavItem( - @SerialName("id") val id: String - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavGetItemContent.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavGetItemContent.kt deleted file mode 100644 index 83d2d938..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavGetItemContent.kt +++ /dev/null @@ -1,65 +0,0 @@ -@file:OptIn(ExperimentalSerializationApi::class) - -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.io.core.ByteReadPacket -import kotlinx.io.core.discardExact -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.decodeFromByteArray -import kotlinx.serialization.json.JsonElement - -import moe.fuqiuluo.qqinterface.servlet.QFavSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.utils.DeflateTools -import moe.fuqiuluo.symbols.OneBotHandler -import moe.fuqiuluo.symbols.decodeProtobuf -import protobuf.fav.WeiyunComm - -@OneBotHandler("fav.get_item_content") -internal object FavGetItemContent: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val id = session.getString("id") - return invoke(id, session.echo) - } - - suspend operator fun invoke( - id: String, - echo: JsonElement = EmptyJsonString - ): String { - val respData = DeflateTools.ungzip(QFavSvc.getItemContent(id).onSuccess { - if (it.mHttpCode != 200 || it.mResult != 0) { - return logic(it.mErrDesc, echo) - } - }.getOrThrow().mRespData) - val readPacket = ByteReadPacket(respData) - readPacket.discardExact(6) - val allLength = readPacket.readInt() - val dataLength = readPacket.readInt() - val headLength = allLength - dataLength - 16 - readPacket.discardExact(2) - ByteArray(headLength).also { - readPacket.readFully(it, 0, it.size) - } - val data = ByteArray(dataLength).also { - readPacket.readFully(it, 0, it.size) - } - - val resp = data.decodeProtobuf().resp!! - return ok(ItemContent( - resp.getFavContentResp!!.content!!.joinToString("") { - String(it.richMedia!!.rawData!!) - } - ), echo) - } - - override val requiredParams: Array = arrayOf("id") - - @Serializable - private data class ItemContent( - @SerialName("content") val content: String - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavGetItemList.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavGetItemList.kt deleted file mode 100644 index 020b2f8c..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/FavGetItemList.kt +++ /dev/null @@ -1,113 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.io.core.ByteReadPacket -import kotlinx.io.core.discardExact -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.decodeFromByteArray -import kotlinx.serialization.json.JsonElement - -import moe.fuqiuluo.qqinterface.servlet.QFavSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.utils.DeflateTools -import moe.fuqiuluo.symbols.OneBotHandler -import moe.fuqiuluo.symbols.decodeProtobuf -import protobuf.fav.WeiyunComm - -@OneBotHandler("fav.get_item_list") -internal object FavGetItemList: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val category = session.getInt("category") - val startPos = session.getInt("start_pos") - val pageSize = session.getInt("page_size") - return invoke(category, startPos, pageSize, session.echo) - } - - suspend operator fun invoke( - category: Int, - startPos: Int, - pageSize: Int, - echo: JsonElement = EmptyJsonString - ): String { - if (pageSize <= 1) { - return logic("page_size must be greater than 1", echo) - } - - val result = DeflateTools.ungzip(QFavSvc.getItemList( - category = category, - startPos = startPos, - pageSize = pageSize - ).onSuccess { - if (it.mHttpCode != 200 || it.mResult != 0) { - return logic("fav.get_item_list failed", echo) - } - }.getOrThrow().mRespData) - val readPacket = ByteReadPacket(result) - readPacket.discardExact(6) - val allLength = readPacket.readInt() - val dataLength = readPacket.readInt() - val headLength = allLength - dataLength - 16 - readPacket.discardExact(2) - ByteArray(headLength).also { - readPacket.readFully(it, 0, it.size) - } - val data = ByteArray(dataLength).also { - readPacket.readFully(it, 0, it.size) - } - val resp = data.decodeProtobuf().resp!!.getFavListResp!! - - val itemList = arrayListOf() - val rawItemList = resp.collections!! - rawItemList.forEach { - val itemId = it.cid - val author = it.author!! - val authorType = author.type.toInt() - val authorId = author.numId.toLong() - val authorName = author.strId - val groupName: String - val groupId: Long - if (authorType == 2) { - groupName = author.groupName - groupId = author.groupId.toLong() - } else { - groupName = "" - groupId = 0L - } - val clientVersion = it.srcAppVer - val time = it.createTime.toLong() - itemList.add(Item( - id = itemId, - authorType = authorType, - author = authorId, - authorName = authorName, - groupName = groupName, - groupId = groupId, - clientVersion = clientVersion, - time = time - )) - } - - return ok(ItemList(itemList), echo) - } - - override val requiredParams: Array = arrayOf("category", "start_pos", "page_size") - - @Serializable - private data class ItemList( - val items: List - ) - - @Serializable - private data class Item( - @SerialName("id") val id: String, - @SerialName("author_type") val authorType: Int, - @SerialName("author") val author: Long, - @SerialName("author_name") val authorName: String, - @SerialName("group_name") val groupName: String, - @SerialName("group_id") val groupId: Long, - @SerialName("client_version") val clientVersion: String, - @SerialName("time") val time: Long - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetCSRF.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetCSRF.kt deleted file mode 100644 index 6462125e..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetCSRF.kt +++ /dev/null @@ -1,31 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.TicketSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.service.data.Credentials -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_csrf_token") -internal object GetCSRF: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val domain = session.getStringOrNull("domain") - ?: return invoke(session.echo) - return invoke(domain, session.echo) - } - - suspend operator fun invoke(domain: String, echo: JsonElement = EmptyJsonString): String { - val uin = TicketSvc.getUin() - val pskey = TicketSvc.getPSKey(uin, domain) - ?: return invoke(echo) - return ok(Credentials(bkn = TicketSvc.getCSRF(pskey)), echo) - } - - operator fun invoke(echo: JsonElement = EmptyJsonString): String { - val uin = TicketSvc.getUin() - val pskey = TicketSvc.getPSKey(uin) - return ok(Credentials(bkn = TicketSvc.getCSRF(pskey)), echo) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetCookies.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetCookies.kt deleted file mode 100644 index 174ca1ca..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetCookies.kt +++ /dev/null @@ -1,32 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.TicketSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.service.data.Credentials -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_cookies", ["get_cookie"]) -internal object GetCookies: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val domain = session.getStringOrNull("domain") - ?: return invoke(session.echo) - return invoke(domain, session.echo) - } - - operator fun invoke(echo: JsonElement = EmptyJsonString): String { - return ok(Credentials( - cookie = TicketSvc.getCookie(), - bigDataTicket = TicketSvc.getBigdataTicket() - ), echo) - } - - suspend operator fun invoke(domain: String, echo: JsonElement = EmptyJsonString): String { - return ok(Credentials( - cookie = TicketSvc.getCookie(domain), - bigDataTicket = TicketSvc.getBigdataTicket() - ), echo) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetCredentials.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetCredentials.kt deleted file mode 100644 index 468b3fb2..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetCredentials.kt +++ /dev/null @@ -1,40 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.TicketSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.service.data.Credentials -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_credentials") -internal object GetCredentials: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val domain = session.getStringOrNull("domain") - ?: return invoke(session.echo) - return invoke(domain, session.echo) - } - - operator fun invoke(echo: JsonElement = EmptyJsonString): String { - val uin = TicketSvc.getUin() - val skey = TicketSvc.getRealSkey(uin) - val pskey = TicketSvc.getPSKey(uin) - return ok( - Credentials( - bkn = TicketSvc.getCSRF(pskey), - cookie = "o_cookie=$uin; ied_qq=o$uin; pac_uid=1_$uin; uin=o$uin; skey=$skey; p_uin=o$uin; p_skey=$pskey;" - ), echo) - } - - suspend operator fun invoke(domain: String, echo: JsonElement = EmptyJsonString): String { - val uin = TicketSvc.getUin() - val skey = TicketSvc.getRealSkey(uin) - val pskey = TicketSvc.getPSKey(uin, domain) ?: "" - val pt4token = TicketSvc.getPt4Token(uin, domain) ?: "" - return ok(Credentials( - bkn = TicketSvc.getCSRF(pskey), - cookie = "o_cookie=$uin; ied_qq=o$uin; pac_uid=1_$uin; uin=o$uin; skey=$skey; p_uin=o$uin; p_skey=$pskey; pt4_token=$pt4token;" - ), echo) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetDeviceBattery.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetDeviceBattery.kt deleted file mode 100644 index a4b0076d..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetDeviceBattery.kt +++ /dev/null @@ -1,19 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_device_battery") -internal object GetDeviceBattery: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - return invoke(session.echo) - } - - operator fun invoke(echo: JsonElement = EmptyJsonString): String { - return ok(PlatformUtils.getDeviceBattery(), echo = echo) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetEssenceMessageList.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetEssenceMessageList.kt deleted file mode 100644 index 9a78f0ae..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetEssenceMessageList.kt +++ /dev/null @@ -1,29 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_essence_msg_list", ["get_essence_message_list"]) -internal object GetEssenceMessageList: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - val page = session.getIntOrNull("page") ?: 0 - val pageSize = session.getIntOrNull("page_size") ?: 20 - return invoke(groupId, page, pageSize, session.echo) - } - - suspend operator fun invoke(groupId: Long, page: Int = 0, pageSize: Int = 20, echo: JsonElement = EmptyJsonString): String { - if (page < 0 || pageSize > 50) { - return badParam("参数不正确:page_size不得大于50", echo) - } - val essenceMessageList = GroupSvc.getEssenceMessageList(groupId, page, pageSize) - if (essenceMessageList.isSuccess) { - return ok(essenceMessageList.getOrNull(), echo) - } - return logic(essenceMessageList.exceptionOrNull()?.message ?: "", echo) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetFile.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetFile.kt deleted file mode 100644 index 7b3a9e45..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetFile.kt +++ /dev/null @@ -1,68 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.service.data.OutResourceByBase64 -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.utils.FileUtils -import moe.fuqiuluo.symbols.OneBotHandler -import java.io.ByteArrayOutputStream -import java.util.Base64 -import java.util.zip.GZIPOutputStream - -@OneBotHandler("get_file") internal object GetFile : IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val file = session.getString("file") - .replace(regex = "[{}\\-]".toRegex(), replacement = "") - .replace(" ", "") - .split(".")[0].lowercase() - val fileType = session.getStringOrNull("file_type") ?: "base64" - return invoke(file, fileType, session.echo) - } - - operator fun invoke(file: String, fileType: String = "base64", echo: JsonElement = EmptyJsonString): String { - val targetFile = FileUtils.getFileByMd5(file) - return if (targetFile.exists()) { - when (fileType) { - "base64", "" -> ok( - OutResourceByBase64( - "/res/${targetFile.nameWithoutExtension}", - Base64.getEncoder() - .encodeToString(targetFile.readBytes()), - targetFile.nameWithoutExtension, - ), echo - ) - - "gzip" -> ok( - OutResourceByBase64( - "/res/${targetFile.nameWithoutExtension}", - compressAndEncode(targetFile.readBytes()), - targetFile.nameWithoutExtension, - ), echo - ) - - else -> error("file_type error", echo) - } - - } else { - error("not found record file from md5", echo) - } - } - - - fun compressAndEncode(input: ByteArray): String { - // 压缩数据 - val outputStream = ByteArrayOutputStream() - val gzip = GZIPOutputStream(outputStream) - gzip.write(input) - gzip.close() - val compressedBytes = outputStream.toByteArray() - - // 编码为 Base64 字符串 - return Base64.getEncoder() - .encodeToString(compressedBytes) - } - - override val requiredParams: Array = arrayOf("file") -} diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetForwardMsg.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetForwardMsg.kt deleted file mode 100644 index cb980e58..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetForwardMsg.kt +++ /dev/null @@ -1,30 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.MsgSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.service.data.GetForwardMsgResult -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_forward_msg", ["get_forward_message"]) -internal object GetForwardMsg : IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val id = session.getString("id") - return invoke(id, session.echo) - } - - suspend operator fun invoke( - resId: String, - echo: JsonElement = EmptyJsonString - ): String { - return ok( - data = GetForwardMsgResult( - msgs = MsgSvc.getForwardMsg(resId).getOrElse { return logic(it.toString(), echo = echo) }), - echo = echo - ) - } - - override val requiredParams: Array = arrayOf("id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetFriendList.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetFriendList.kt deleted file mode 100644 index ff9bfefa..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetFriendList.kt +++ /dev/null @@ -1,37 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.service.data.FriendEntry -import moe.fuqiuluo.shamrock.remote.service.data.PlatformType -import moe.fuqiuluo.qqinterface.servlet.FriendSvc -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_friend_list") -internal object GetFriendList: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val refresh = session.getBooleanOrDefault("refresh", session.getBooleanOrDefault("no_cache", false)) - return invoke(refresh, session.echo) - } - - suspend operator fun invoke(refresh: Boolean, echo: JsonElement = EmptyJsonString): String { - val friendList = FriendSvc.getFriendList(refresh).onFailure { - return error(it.message ?: "unknown error", echo, arrayResult = true) - }.getOrThrow() - return ok(friendList.map { friend -> - FriendEntry( - id = friend.uin.toLong(), - name = friend.name, - displayName = friend.remark, - remark = friend.remark, - age = friend.age, - gender = friend.gender, - groupId = friend.groupid, - platformType = PlatformType.valueOf(friend.iTermType), - termType = friend.iTermType - ) - }, echo) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetFriendSystemMsg.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetFriendSystemMsg.kt deleted file mode 100644 index 9807467f..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetFriendSystemMsg.kt +++ /dev/null @@ -1,45 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.FriendSvc -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.service.data.FriendRequest -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_friend_system_msg", ["get_friend_system_message"]) -internal object GetFriendSystemMsg : IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - return invoke(echo = session.echo) - } - - suspend operator fun invoke(echo: JsonElement = EmptyJsonString): String { - val list = FriendSvc.requestFriendSystemMsgNew(20) - val msgs = list - // 13 是加别人好友 - ?.filter { it.msg.sub_type.get() != 13 } - ?.map { - LogCenter.log(it.toString(), Level.WARN) - FriendRequest( - seq = it.msg_seq.get(), - userId = it.req_uin.get(), - name = it.msg.req_uin_nick.get(), - source = it.msg.msg_source.get(), - subId = it.msg.src_id.get(), - subSrcId = it.msg.sub_src_id.get(), - msg = it.msg.msg_additional.get(), - sourceGroupName = it.msg.group_name.get(), - sourceGroupCode = it.msg.group_code.get(), - flag = "${it.msg_seq.get()};${it.msg.src_id.get()};${it.msg.sub_src_id.get()};${it.req_uin.get()}", - sex = if (it.msg.req_uin_gender.get() == 1) "female" else "male", - age = it.msg.req_uin_age.get(), - msgDetail = it.msg.msg_detail.get(), - status = it.msg.msg_decided.get() - ) - } ?: mutableListOf() - return ok(msgs, echo = echo) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGProChannelList.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGProChannelList.kt deleted file mode 100644 index 5825a65c..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGProChannelList.kt +++ /dev/null @@ -1,38 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.coroutines.withTimeoutOrNull -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GProSvc -import moe.fuqiuluo.qqinterface.servlet.structures.GProChannelInfo -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_guild_channel_list") -internal object GetGProChannelList: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val guildId = session.getString("guild_id") - val refresh = session.getBooleanOrDefault("refresh", session.getBooleanOrDefault("no_cache", false)) - return invoke(guildId.toULong(), refresh, echo = session.echo) - } - - suspend operator fun invoke(guildId: ULong, refresh: Boolean, echo: JsonElement = EmptyJsonString): String { - val result = withTimeoutOrNull(5000) { - GProSvc.getChannelList(guildId, refresh) - } ?: return error("timeout", echo) - result.onFailure { - return error(it.message ?: "unable to fetch channel list", echo) - } - return ok(GuildChannelListResult(result.getOrThrow()), echo, "success") - } - - override val requiredParams: Array = arrayOf("guild_id") - - @Serializable - data class GuildChannelListResult( - @SerialName("channel_list") val channelList: List - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGroupFileSystemInfo.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGroupFileSystemInfo.kt deleted file mode 100644 index 4091b5be..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGroupFileSystemInfo.kt +++ /dev/null @@ -1,22 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.FileSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_group_file_system_info") -internal object GetGroupFileSystemInfo: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - return invoke(groupId, session.echo) - } - - suspend operator fun invoke(groupId: Long, echo: JsonElement = EmptyJsonString): String { - return ok(data = FileSvc.getGroupFileSystemInfo(groupId), echo) - } - - override val requiredParams: Array = arrayOf("group_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGroupFileUrl.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGroupFileUrl.kt deleted file mode 100644 index 50e1a6e4..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGroupFileUrl.kt +++ /dev/null @@ -1,24 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.FileSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_group_file_url") -internal object GetGroupFileUrl: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - val fileId = session.getString("file_id") - val busid = session.getInt("busid") - return invoke(groupId, fileId, busid, session.echo) - } - - suspend operator fun invoke(groupId: Long, fileId: String, busid: Int, echo: JsonElement = EmptyJsonString): String { - return ok(data = FileSvc.getGroupFileInfo(groupId, fileId, busid), echo = echo) - } - - override val requiredParams: Array = arrayOf("group_id", "file_id", "busid") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGroupMsgHistory.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGroupMsgHistory.kt deleted file mode 100644 index c3b182f4..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGroupMsgHistory.kt +++ /dev/null @@ -1,29 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import moe.fuqiuluo.shamrock.helper.db.MessageDB -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_group_msg_history", ["get_group_message_history"]) -internal object GetGroupMsgHistory: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - val cnt = session.getIntOrNull("count") ?: 20 - val startId = session.getIntOrNull("message_id")?.let { - if (it == 0) return@let 0L - MessageDB.getInstance() - .messageMappingDao() - .queryByMsgHashId(it)?.qqMsgId - } ?: session.getIntOrNull("message_seq")?.let { - if (it == 0) return@let 0L - MessageDB.getInstance() - .messageMappingDao() - .queryByMsgSeq(MsgConstant.KCHATTYPEGROUP, groupId.toString(), it)?.qqMsgId - } ?: 0L - return GetHistoryMsg("group", groupId.toString(), cnt, startId, session.echo) - } - - override val requiredParams: Array = arrayOf("group_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGroupNotice.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGroupNotice.kt deleted file mode 100644 index 54dd5ccf..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGroupNotice.kt +++ /dev/null @@ -1,25 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("_get_group_notice", ["get_group_notice"]) -internal object GetGroupNotice: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - return invoke(groupId, session.echo) - } - - suspend operator fun invoke(groupId: Long, echo: JsonElement = EmptyJsonString): String { - val announcements = GroupSvc.getGroupAnnouncements(groupId) - if (announcements.isSuccess) { - return ok(announcements.getOrNull(), echo) - } - return logic(announcements.exceptionOrNull()?.message ?: "", echo) - - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGroupRemainAtAllRemain.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGroupRemainAtAllRemain.kt deleted file mode 100644 index b837da03..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGroupRemainAtAllRemain.kt +++ /dev/null @@ -1,29 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_group_at_all_remain") -internal object GetGroupRemainAtAllRemain: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - return invoke(groupId, session.echo) - } - - suspend operator fun invoke( - groupId: Long, - echo: JsonElement = EmptyJsonString - ): String { - val result = GroupSvc.getGroupRemainAtAllRemain(groupId) - if (result.isFailure) { - return error(result.exceptionOrNull()?.message ?: "获取群 @全体成员 剩余次数失败", echo, arrayResult = true) - } - return ok(result.getOrThrow(), echo) - } - - override val requiredParams: Array = arrayOf("group_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGroupRootFiles.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGroupRootFiles.kt deleted file mode 100644 index 80b8a1bb..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGroupRootFiles.kt +++ /dev/null @@ -1,25 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.FileSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_group_root_files") -internal object GetGroupRootFiles : IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - return invoke(groupId, session.echo) - } - - suspend operator fun invoke(groupId: Long, echo: JsonElement = EmptyJsonString): String { - return ok( - FileSvc.getGroupRootFiles(groupId).getOrElse { return error(why = "获取失败: $it", echo = echo) }, - echo = echo - ) - } - - override val requiredParams: Array = arrayOf("group_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGroupSubFiles.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGroupSubFiles.kt deleted file mode 100644 index cad77f90..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGroupSubFiles.kt +++ /dev/null @@ -1,26 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.FileSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_group_files_by_folder") -internal object GetGroupSubFiles : IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - val folderId = session.getString("folder_id") - return invoke(groupId, folderId, session.echo) - } - - suspend operator fun invoke(groupId: Long, folderId: String, echo: JsonElement = EmptyJsonString): String { - return ok( - FileSvc.getGroupFiles(groupId, folderId).getOrElse { return error(why = "获取失败: $it", echo = echo) }, - echo = echo - ) - } - - override val requiredParams: Array = arrayOf("group_id", "folder_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGroupSystemMsg.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGroupSystemMsg.kt deleted file mode 100644 index 82b69e52..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGroupSystemMsg.kt +++ /dev/null @@ -1,65 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.service.data.GroupRequest -import moe.fuqiuluo.shamrock.remote.service.data.GroupSystemMessage -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_group_system_msg", ["get_group_system_message"]) -internal object GetGroupSystemMsg: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - return invoke(echo = session.echo) - } - - suspend operator fun invoke(echo: JsonElement = EmptyJsonString): String { - val list = GroupSvc.requestGroupSystemMsgNew(20) - val riskList = GroupSvc.requestGroupSystemMsgNew(20, 2) - val msgs = GroupSystemMessage( - invited = mutableListOf(), - join = mutableListOf() - ) - (list + riskList).forEach { - when(it.msg.group_msg_type.get()) { - 22, 1 -> { - // join 进群消息 - msgs.join += GroupRequest ( - msgSeq = it.msg_seq.get(), - invitorUin = it.msg.action_uin.get(), - invitorNick = it.msg.action_uin_nick.get(), - groupId = it.msg.group_code.get(), - groupName = it.msg.group_name.get(), - checked = it.msg.msg_decided.get().isNotBlank(), - actor = it.msg.actor_uin.get(), - requesterUin = it.req_uin.get(), - requesterNick = it.msg.req_uin_nick.get(), - message = it.msg.msg_additional.get(), - flag = "${it.msg_seq.get()};${it.msg.group_code.get()};${it.req_uin.get()}" - ) - } - 2 -> { - // invite 别人邀请我 - msgs.invited += GroupRequest ( - msgSeq = it.msg_seq.get(), - invitorUin = it.msg.action_uin.get(), - invitorNick = it.msg.action_uin_nick.get(), - groupId = it.msg.group_code.get(), - groupName = it.msg.group_name.get(), - checked = it.msg.msg_decided.get().isNotBlank(), - actor = it.msg.actor_uin.get(), - requesterUin = 0, - requesterNick = "", - message = it.msg.msg_additional.get(), - flag = "${it.msg_seq.get()};${it.msg.group_code.get()};${it.req_uin.get()}" - ) - } - - else -> {} - } - } - return ok(msgs, echo = echo) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGuildFeeds.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGuildFeeds.kt deleted file mode 100644 index 84fabd56..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGuildFeeds.kt +++ /dev/null @@ -1,38 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GProSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler -import protobuf.guild.StFeed - -@OneBotHandler("get_guild_feeds") -internal object GetGuildFeeds: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val guildId = session.getString("guild_id").toULong() - val channelId = session.getStringOrNull("channel_id")?.toULong() ?: 0uL - val from = session.getIntOrNull("from") ?: 0 - return invoke(guildId, channelId, from, session.echo) - } - - suspend operator fun invoke(guildId: ULong, channelId: ULong, startIndex: Int, echo: JsonElement = EmptyJsonString): String { - val result = GProSvc.getGuildFeeds(guildId, channelId, startIndex).getOrElse { - GProSvc.getGuildFeeds(guildId, 0uL, startIndex).onFailure { - return error(it.message ?: "server error", echo) - }.getOrThrow() - } - if (result.vecFeed == null) { - return error("server error", echo) - } - return ok(GetGuildFeedsResult(result.isFinish == 1, result.vecFeed!!), echo = echo) - } - - @Serializable - data class GetGuildFeedsResult( - val isFinish: Boolean, - val feeds: List - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGuildList.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGuildList.kt deleted file mode 100644 index 91f4b94f..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGuildList.kt +++ /dev/null @@ -1,35 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GProSvc -import moe.fuqiuluo.qqinterface.servlet.structures.GuildInfo -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonArray -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher -import moe.fuqiuluo.symbols.OneBotHandler -import mqq.app.MobileQQ - -@OneBotHandler("get_guild_list") -internal object GetGuildList : IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val oldSdk = session.getBooleanOrDefault("old_sdk", false) - val refresh = session.getBooleanOrDefault("refresh", session.getBooleanOrDefault("no_cache", false)) - return invoke(refresh, oldSdk, echo = session.echo) - } - - operator fun invoke(refresh: Boolean = true, oldSdk: Boolean, echo: JsonElement = EmptyJsonString): String { - val result = GProSvc.getGuildList(refresh, oldSdk) - return ok(GuildListResult(result), echo, "success") - } - - @Serializable - data class GuildListResult( - @SerialName("guild_list") var guildList: List = arrayListOf() - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGuildMemberList.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGuildMemberList.kt deleted file mode 100644 index 3265dfc2..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGuildMemberList.kt +++ /dev/null @@ -1,79 +0,0 @@ -@file:OptIn(ExperimentalSerializationApi::class) - -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.decodeFromByteArray -import kotlinx.serialization.encodeToByteArray -import kotlinx.serialization.json.JsonElement - -import moe.fuqiuluo.qqinterface.servlet.GProSvc -import moe.fuqiuluo.qqinterface.servlet.structures.GetGuildMemberListNextToken -import moe.fuqiuluo.qqinterface.servlet.structures.GuildMemberInfo -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.tools.hex2ByteArray -import moe.fuqiuluo.shamrock.tools.toHexString -import moe.fuqiuluo.symbols.OneBotHandler -import moe.fuqiuluo.symbols.decodeProtobuf -import protobuf.auto.toByteArray - -@OneBotHandler("get_guild_member_list") -internal object GetGuildMemberList: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val guildId = session.getString("guild_id") - val all = session.getBooleanOrDefault("all", false) - return invoke(guildId.toULong(), all, session.getStringOrNull("next_token") ?: "", session.echo) - } - - suspend operator fun invoke(guildId: ULong, all: Boolean, nextTokenStr: String, echo: JsonElement = EmptyJsonString): String { - val curNextToken = if (nextTokenStr.isEmpty()) null else nextTokenStr.hex2ByteArray().decodeProtobuf() - val result = GProSvc.getGuildMemberList( - guildId = guildId, - fetchAll = all, - startIndex = curNextToken?.startIndex ?: 0, - roleIndex = curNextToken?.roleIndex ?: 1, - count = 50 - ) - result.onFailure { - return error(it.message ?: "unable to fetch guild member list", echo) - } - val nextToken = result.getOrThrow().first - val members = arrayListOf() - result.getOrThrow().second.forEach { - it.members.forEach { user -> - members.add(GuildMemberInfo( - tinyId = user.tinyId, - title = user.memberName, - nickname = user.nickName, - roleId = user.roleManagementTag.roleId, - roleName = user.roleManagementTag.tagName, - roleColor = user.roleManagementTag.color, - joinTime = user.joinTime, - robotType = user.robotType, - type = user.type, - inBlack = user.inBlack, - platform = user.platform - )) - } - } - return ok(GetGuildMemberListResult( - members = members, - finish = nextToken.finish, - nextToken = nextToken.toByteArray().toHexString(), - ), echo) - } - - override val requiredParams: Array = arrayOf("guild_id") - - @Serializable - data class GetGuildMemberListResult( - @SerialName("members") val members: List, - @SerialName("next_token") val nextToken: String, - @SerialName("finished") val finish: Boolean - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGuildMemberProfile.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGuildMemberProfile.kt deleted file mode 100644 index f97a66b1..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGuildMemberProfile.kt +++ /dev/null @@ -1,78 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GProSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_guild_member_profile") -internal object GetGuildMemberProfile: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val guildId = session.getString("guild_id").toULong() - val userId = session.getLong("user_id").toULong() - return invoke(guildId, userId, session.echo) - } - - suspend operator fun invoke(guildId: ULong, userId: ULong, echo: JsonElement = EmptyJsonString): String { - val userResult = GProSvc.getUserGuildInfo(guildId, userId).onFailure { - return error(it.message ?: "unable to fetch guild member info", echo) - }.getOrThrow() - val roles = GProSvc.fetchGuildMemberRoles(guildId, userId).onFailure { - return error(it.message ?: "unable to fetch guild member roles", echo) - }.getOrThrow() - - return ok(GetGuildMemberInfo( - tinyId = userResult.memberTinyid, - nickname = userResult.nickName ?: "", - avatarUrl = userResult.url ?: "", - joinTime = userResult.joinTime, - roles = roles.map { - RoleInfo( - roleId = it.roleId.toString(), - roleName = it.name.ifNullOrEmpty(it.levelDsc.ifNullOrEmpty(it.displayTagName ?: ""))!!, - color = it.color, - permission = it.rolePermissions.permissionList.map { - Permission( - rootId = it.rootId, - childIds = it.childIds ?: emptyList() - ) - }, - type = it.type, - displayName = it.displayTagName ?: "" - ) - } - ), echo = echo) - } - - override val requiredParams: Array = arrayOf("guild_id", "user_id") - - @Serializable - data class GetGuildMemberInfo( - @SerialName("tiny_id") val tinyId: ULong, - @SerialName("nickname") val nickname: String, - @SerialName("avatar_url") val avatarUrl: String, - @SerialName("join_time") val joinTime: ULong, - @SerialName("roles") val roles: List - ) - - @Serializable - data class RoleInfo( - @SerialName("role_id") val roleId: String, - @SerialName("role_name") val roleName: String, - @SerialName("color") val color: Long, - @SerialName("permission") val permission: List, - @SerialName("type") val type: Int, - @SerialName("display_name") val displayName: String - ) - - @Serializable - data class Permission( - @SerialName("root_id") val rootId: Int, - @SerialName("child_ids") val childIds: List - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGuildMetaByGuest.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGuildMetaByGuest.kt deleted file mode 100644 index b7d08983..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGuildMetaByGuest.kt +++ /dev/null @@ -1,59 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.protobuf.ProtoNumber -import moe.fuqiuluo.qqinterface.servlet.GProSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_guild_meta_by_guest") -internal object GetGuildMetaByGuest: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val guildId = session.getString("guild_id").toULong() - return invoke(guildId, session.echo) - } - - suspend operator fun invoke(guildId: ULong, echo: JsonElement = EmptyJsonString): String { - val result = GProSvc.getGuildInfo(guildId) - result.onFailure { - return error(it.message ?: "unable to fetch guild info", echo) - } - val info = result.getOrThrow() - if (info.meta == null) { - return error("unable to fetch guild meta", echo) - } - val meta = info.meta!! - return ok(GetGuildMetaByGuestResponse( - guildId = info.guildId, - guildName = meta.name ?: "", - guildProfile = meta.profile ?: "", - createTime = meta.createTime, - maxMemberCount = meta.maxMemberCount, - maxRobotCount = meta.robotMaxNum, - maxAdminCount = meta.adminMaxNum, - memberCount = meta.memberCount, - ownerId = meta.ownerId, - guildDisplayId = meta.displayId ?: "" - ), echo) - } - - override val requiredParams: Array = arrayOf("guild_id") - - @Serializable - data class GetGuildMetaByGuestResponse( - @SerialName("guild_id") val guildId: ULong, - @SerialName("guild_name") val guildName: String, - @SerialName("guild_profile") val guildProfile: String, - @SerialName("create_time") val createTime: Long, - @SerialName("max_member_count") val maxMemberCount: Long, - @SerialName("max_robot_count") val maxRobotCount: Int, - @SerialName("max_admin_count") val maxAdminCount: Int, - @SerialName("member_count") val memberCount: Long, - @SerialName("owner_id") val ownerId: ULong, - @SerialName("guild_display_id") val guildDisplayId: String - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGuildRoles.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGuildRoles.kt deleted file mode 100644 index 830fd0d1..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGuildRoles.kt +++ /dev/null @@ -1,61 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GProSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.action.handlers.GetGuildMemberProfile.Permission -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_guild_roles") -internal object GetGuildRoles: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val guildId = session.getString("guild_id").toULong() - return invoke(guildId, session.echo) - } - - suspend operator fun invoke(guildId: ULong, echo: JsonElement = EmptyJsonString): String { - val result = GProSvc.getGuildRoles(guildId).onFailure { - return error(it.message ?: "unable to fetch guild roles", echo) - }.getOrThrow() - return ok(GetGuildRolesResult(result.map { - GuildRole( - color = it.color, - disabled = it.bHoist, - independent = it.isChannelRole, - maxCount = it.memberLimit, - memberCount = it.count, - owned = it.isNotSort, - roleId = it.roleId, - roleName = it.name, - permission = it.rolePermissions.permissionList.map { - Permission(it.rootId, it.childIds) - }, - ) - }), echo = echo) - } - - override val requiredParams: Array = arrayOf("guild_id") - - @Serializable - data class GetGuildRolesResult( - @SerialName("roles") val roles: List - ) - - @Serializable - data class GuildRole( - @SerialName("argb_color") val color: Long, - @SerialName("disabled") val disabled: Boolean, - @SerialName("independent") val independent: Boolean, - @SerialName("max_count") val maxCount: Int, - @SerialName("member_count") val memberCount: Int, - @SerialName("owned") val owned: Boolean, - @SerialName("role_id") val roleId: Long, - @SerialName("role_name") val roleName: String, - @SerialName("permission") val permission: List, - ) - -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGuildServiceProfile.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGuildServiceProfile.kt deleted file mode 100644 index af98a28a..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetGuildServiceProfile.kt +++ /dev/null @@ -1,39 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GProSvc -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_guild_service_profile") -internal object GetGuildServiceProfile : IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - return invoke(echo = session.echo) - } - - suspend operator fun invoke(echo: JsonElement = EmptyJsonString): String { - val result = GProSvc.getSelfGuildInfo() - result.onFailure { - return error(it.message ?: "unable to fetch self guild info", echo) - } - val info = result.getOrThrow() - //LogCenter.log(info.toString()) - return ok(GuildServiceProfile( - nickName = info.nickName ?: info.memberName ?: "", - tinyId = info.memberTinyid, - avatarUrl = info.url ?: "" - ), echo = echo) - } - - @Serializable - data class GuildServiceProfile( - @SerialName("nickname") val nickName: String, - @SerialName("tiny_id") val tinyId: ULong, - @SerialName("avatar_url") val avatarUrl: String, - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetHistoryMsg.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetHistoryMsg.kt deleted file mode 100644 index e6bfc60a..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetHistoryMsg.kt +++ /dev/null @@ -1,133 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import com.tencent.qqnt.kernel.nativeinterface.MsgRecord -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.MsgSvc -import moe.fuqiuluo.qqinterface.servlet.msg.toSegments -import moe.fuqiuluo.qqinterface.servlet.msg.toListMap -import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.helper.db.MessageDB -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.service.data.MessageDetail -import moe.fuqiuluo.shamrock.remote.service.data.MessageSender -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher -import moe.fuqiuluo.symbols.OneBotHandler -import java.util.ArrayList -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine - -@OneBotHandler("get_history_msg", ["get_history_message"]) -internal object GetHistoryMsg : IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val msgType = session.getString("message_type") - val peerId = session.getLong(if (msgType == "group") "group_id" else "user_id").toString() - val cnt = session.getIntOrNull("count") ?: 20 - - val startId = session.getIntOrNull("message_id")?.let { - if (it == 0) return@let 0L - MessageDB.getInstance() - .messageMappingDao() - .queryByMsgHashId(it)?.qqMsgId - } ?: session.getIntOrNull("message_seq")?.let { - if (it == 0) return@let 0L - MessageDB.getInstance() - .messageMappingDao() - .queryByMsgSeq(MessageHelper.obtainMessageTypeByDetailType(msgType), peerId, it)?.qqMsgId - } ?: 0L - - return invoke(msgType, peerId, cnt, startId, echo = session.echo) - } - - suspend operator fun invoke( - msgType: String, - peerId: String, - cnt: Int, - startMsgId: Long = 0, - echo: JsonElement = EmptyJsonString - ): String { - val msgService = NTServiceFetcher.kernelService.wrapperSession.msgService - val chatType = MessageHelper.obtainMessageTypeByDetailType(msgType) - val contact = MessageHelper.generateContact(chatType, peerId) - val result = suspendCoroutine { - msgService.getMsgs(contact, startMsgId, cnt, true) { code, why, msgs -> - it.resume(GetMsgResult(code, why, msgs)) - } - } - if (result.code != 0) { - return logic(result.msg ?: "获取历史消息失败", echo = echo) - } - - val msgList = ArrayList().apply { - addAll(result.data!!.map { msg -> - val msgHash = MessageHelper.generateMsgIdHash(msg.chatType, msg.msgId) - MessageDetail( - time = msg.msgTime.toInt(), - msgType = MessageHelper.obtainDetailTypeByMsgType(msg.chatType), - msgId = msgHash, - qqMsgId = msg.msgId, - msgSeq = msg.msgSeq, - realId = msg.msgSeq, - sender = MessageSender( - msg.senderUin, msg.sendNickName, "unknown", 0, msg.senderUid, msg.senderUid - ), - message = msg.elements.toSegments( - msg.chatType, - if (msg.chatType == MsgConstant.KCHATTYPEGUILD) msg.guildId else msg.peerUin.toString(), - msg.channelId ?: msg.peerUin.toString() - ).toListMap(), - peerId = msg.peerUin, - groupId = if (msg.chatType == MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0, - targetId = if (msg.chatType != MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0 - ) - }) - if (startMsgId != 0L) { - val msg = MsgSvc.getMsgByQMsgId(chatType, peerId, startMsgId).onFailure { - return logic("Obtain msg failed, please check your msg_id.", echo) - }.getOrThrow() - add(MessageDetail( - time = msg.msgTime.toInt(), - msgType = MessageHelper.obtainDetailTypeByMsgType(msg.chatType), - msgId = MessageHelper.generateMsgIdHash(msg.chatType, msg.msgId), - qqMsgId = msg.msgId, - msgSeq = msg.msgSeq, - realId = msg.msgSeq, - sender = MessageSender( - msg.senderUin, msg.sendNickName - .ifEmpty { msg.sendMemberName } - .ifEmpty { msg.sendRemarkName } - .ifEmpty { msg.peerName }, "unknown", 0, msg.senderUid, msg.senderUid - ), - message = msg.elements.toSegments( - chatType, - if (chatType == MsgConstant.KCHATTYPEGUILD) msg.guildId else msg.peerUin.toString(), - msg.channelId ?: peerId - ).toListMap(), - peerId = msg.peerUin, - groupId = if (msg.chatType == MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0, - targetId = if (msg.chatType != MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0 - ) - ) - } - } - - return ok(data = GetHistoryMsgResult(msgList), echo = echo) - } - - override val requiredParams: Array = arrayOf("message_type") - - @Serializable - data class GetHistoryMsgResult( - @SerialName("messages") val msgs: List - ) - - data class GetMsgResult( - val code: Int, - val msg: String?, - val data: ArrayList? - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetHttpCookies.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetHttpCookies.kt deleted file mode 100644 index 4af7af6e..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetHttpCookies.kt +++ /dev/null @@ -1,29 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.TicketSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.service.data.Credentials -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_http_cookies") -internal object GetHttpCookies : IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val appid = session.getString("appid") - val daid = session.getString("daid") - val jumpurl = session.getString("jumpurl") - return invoke(appid, daid, jumpurl, session.echo) - } - - suspend operator fun invoke( - appid: String, - daid: String, - jumpurl: String, - echo: JsonElement = EmptyJsonString - ): String { - val ck = TicketSvc.GetHttpCookies(appid, daid, jumpurl) ?: "" - return ok(Credentials(cookie = ck), echo) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetImage.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetImage.kt deleted file mode 100644 index be2c23b1..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetImage.kt +++ /dev/null @@ -1,73 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.TicketSvc -import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc -import moe.fuqiuluo.shamrock.helper.db.ImageDB -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_image", ["get_img"]) -internal object GetImage: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val echo = session.echo - val file = session.getString("file") - return invoke(file, echo) - } - - suspend operator fun invoke(file: String, echo: JsonElement = EmptyJsonString): String { - val fileMd5 = file - .replace("{", "") - .replace("}", "") - .replace("-", "") - .split(".")[0].uppercase().trim() - if (fileMd5.length != 32) { - return badParam("图片缓存文件名不合法", echo = echo) - } - - val image = ImageDB.getInstance().imageMappingDao().queryByFileName(fileMd5) - ?: return logic("只能查询已缓存的图片", echo = echo) - - return ok(GetImageResult( - image.size, - image.fileName, - when(image.chatType) { - MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl( - originalUrl = "", - md5 = fileMd5, - fileSize = image.size.toULong(), - sha = "", - fileId = image.fileId, - width = 100u, - height = 100u, - peer = TicketSvc.getUin() - ) - MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl( - originalUrl = "", - md5 = fileMd5, - fileSize = image.size.toULong(), - sha = "", - fileId = image.fileId, - storeId = image.storeId, - width = 100u, - height = 100u, - peer = TicketSvc.getUin() - ) - else -> error("Not supported chat type: ${image.chatType}, convertMsgElementsToMsgSegment::Pic") - } - ), echo = echo) - } - - override val requiredParams: Array = arrayOf("file") - - @Serializable - data class GetImageResult( - val size: Long, - val filename: String, - val url: String - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetLatestEvents.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetLatestEvents.kt deleted file mode 100644 index 41b1f0da..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetLatestEvents.kt +++ /dev/null @@ -1,17 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.structures.EmptyObject -import moe.fuqiuluo.shamrock.remote.structures.Status -import moe.fuqiuluo.shamrock.remote.structures.resultToString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_latest_events") -internal object GetLatestEvents: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - return resultToString( - true, Status.Ok, listOf(), echo = session.echo - ) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetLoginInfo.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetLoginInfo.kt deleted file mode 100644 index 1e6e5f63..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetLoginInfo.kt +++ /dev/null @@ -1,33 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import com.tencent.mobileqq.app.QQAppInterface -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.structures.StdAccount -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher -import moe.fuqiuluo.symbols.OneBotHandler -import mqq.app.MobileQQ - -@OneBotHandler("get_login_info") -internal object GetLoginInfo: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - return invoke(session.echo) - } - - operator fun invoke(echo: JsonElement = EmptyJsonString): String { - val accounts = MobileQQ.getMobileQQ().allAccounts - val runtime = AppRuntimeFetcher.appRuntime - val curUin = runtime.currentAccountUin - val account = accounts.firstOrNull { it.uin == curUin } - return if (account == null || !account.isLogined) { - error("当前不处于已登录状态", echo = echo) - } else { - ok( - StdAccount( - curUin.toLong(),if (runtime is QQAppInterface) runtime.currentNickname else "unknown" - ), echo = echo) - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetModelShow.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetModelShow.kt deleted file mode 100644 index dc37988e..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetModelShow.kt +++ /dev/null @@ -1,31 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.CardSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_model_show") -internal object GetModelShow: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val uin = session.getLongOrNull("user_id") - return if (uin == null) { - invoke(session.echo) - } else { - invoke(uin, session.echo) - } - } - - suspend operator fun invoke(echo: JsonElement = EmptyJsonString): String { - return ok(CardSvc.getModelShow(), echo) - } - - suspend operator fun invoke(uin: Long, echo: JsonElement = EmptyJsonString): String { - if (uin == 0L) { - return invoke(echo) - } - return ok(CardSvc.getModelShow(uin), echo) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetModelShowList.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetModelShowList.kt deleted file mode 100644 index 49963e7e..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetModelShowList.kt +++ /dev/null @@ -1,115 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import io.ktor.client.request.get -import io.ktor.client.request.header -import io.ktor.client.request.parameter -import io.ktor.client.statement.bodyAsText -import io.ktor.client.statement.request -import io.ktor.http.HttpStatusCode -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.GlobalClient -import moe.fuqiuluo.shamrock.tools.GlobalJson -import moe.fuqiuluo.shamrock.tools.json -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.qqinterface.servlet.TicketSvc -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("_get_model_show") -internal object GetModelShowList : IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - return invoke(session.getString("model"), session.echo) - } - - suspend operator fun invoke(model: String, echo: JsonElement = EmptyJsonString): String { - val ts = System.currentTimeMillis() / 1000 - val csrf = TicketSvc.getCSRF(TicketSvc.getUin(), "vip.qq.com") - - val req = mapOf( - "13030" to mapOf( - "req" to mapOf( - "lUin" to TicketSvc.getUin().toLong(), - "sModel" to model.replace("+", "%20"), - "iAppType" to 0, - "sIMei" to "", - "bShowInfo" to true, - "sModelShow" to "", - "bRecoverDefault" to false - ) - ) - ).json.toString() - - val resp = GlobalClient.get("https://proxy.vip.qq.com/cgi-bin/srfentry.fcgi") { - parameter("ts", ts) - parameter("daid", 18) - parameter("g_tk", csrf) - parameter("pt4_token", "") - parameter("data", req) - val cookie = TicketSvc.getCookie("vip.qq.com") - header("Cookie", cookie) - } - - if (resp.status != HttpStatusCode.OK) { - LogCenter.log({ "unable to fetch model show list: ${resp.request.url} => ${resp.status}" }, Level.DEBUG) - return error("unable to fetch model show list: ${resp.status}", echo) - } - - val json = kotlin.runCatching { - GlobalJson.decodeFromString(resp.bodyAsText()) - }.onFailure { - it.printStackTrace() - }.getOrNull() - - if (json?.resp == null) { - return error("unable to fetch model show list", echo) - } - - return ok(GetModelListResp(json.resp.data.rsp.vItemList.map { - Model(it.sModelShow, it.bNeedPay) - }), echo) - } - - override val requiredParams: Array = arrayOf("model") - - @Serializable - data class GetModelListResp( - @SerialName("variants") val resp: List - ) - - @Serializable - data class Model( - @SerialName("model_show") val model: String, - @SerialName("need_pay") val needPay: Boolean - ) - - @Serializable - data class ModelShowStruct( - @SerialName("13030") val resp: ModelGetResp? = null - ) - - @Serializable - data class ModelGetResp( - @SerialName("data") val data: ModelGetData - ) - - @Serializable - data class ModelGetData( - @SerialName("rsp") val rsp: ModelGetRsp - ) - - @Serializable - data class ModelGetRsp( - @SerialName("vItemList") val vItemList: List - ) - - @Serializable - data class ModelGetItem( - @SerialName("sModelShow") val sModelShow: String, - @SerialName("bNeedPay") val bNeedPay: Boolean - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetMsg.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetMsg.kt deleted file mode 100644 index c7dfbe89..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetMsg.kt +++ /dev/null @@ -1,56 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.service.data.MessageDetail -import moe.fuqiuluo.shamrock.remote.service.data.MessageSender -import moe.fuqiuluo.qqinterface.servlet.MsgSvc -import moe.fuqiuluo.qqinterface.servlet.msg.toSegments -import moe.fuqiuluo.qqinterface.servlet.msg.toListMap -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_message", ["get_msg"]) -internal object GetMsg: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val hashCode = session.getIntOrNull("message_id") - ?: session.getInt("msg_id") - return invoke(hashCode, session.echo) - } - - suspend operator fun invoke(msgHash: Int, echo: JsonElement = EmptyJsonString): String { - val msg = MsgSvc.getMsg(msgHash).onFailure { - return logic("Obtain msg failed, please check your msg_id.", echo) - }.getOrThrow() - return ok(MessageDetail( - time = msg.msgTime.toInt(), - msgType = MessageHelper.obtainDetailTypeByMsgType(msg.chatType), - msgId = msgHash, - qqMsgId = msg.msgId, - msgSeq = msg.msgSeq, - realId = msg.msgSeq, - sender = MessageSender( - msg.senderUin, msg.sendNickName - .ifEmpty { msg.sendMemberName } - .ifEmpty { msg.sendRemarkName } - .ifEmpty { msg.peerName }, "unknown", - 0, - msg.senderUid, - msg.senderUid - ), - message = msg.elements.toSegments( - msg.chatType, - if (msg.chatType == MsgConstant.KCHATTYPEGUILD) msg.guildId else msg.peerUin.toString(), - msg.channelId ?: msg.peerUin.toString() - ).toListMap(), - peerId = msg.peerUin, - groupId = if (msg.chatType == MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0, - targetId = if (msg.chatType != MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0 - ), echo) - } - - override val requiredParams: Array = arrayOf("message_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetNotJoinedGroupInfo.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetNotJoinedGroupInfo.kt deleted file mode 100644 index 5c60fcb6..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetNotJoinedGroupInfo.kt +++ /dev/null @@ -1,25 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_not_joined_group_info") -internal object GetNotJoinedGroupInfo: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - return invoke(groupId, session.echo) - } - - suspend operator fun invoke(groupId: Long, echo: JsonElement = EmptyJsonString): String { - GroupSvc.getNotJoinedGroupInfo(groupId = groupId).onSuccess { - return ok(it, echo = echo) - }.exceptionOrNull()?.let { - return error(it.message ?: "无法获取群信息", echo = echo) - } - return logic("Unable to obtain group information", echo) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetOnlineClients.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetOnlineClients.kt deleted file mode 100644 index 666c997a..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetOnlineClients.kt +++ /dev/null @@ -1,38 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.QSafeSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("_get_online_clients") -internal object GetOnlineClients: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - return invoke(session.echo) - } - - suspend operator fun invoke(echo: JsonElement = EmptyJsonString): String { - val clients = QSafeSvc.getOnlineClients() - ?: return logic("获取在线设备信息失败", echo, arraayResult = true) - return ok(clients.map { - DevInfo(it.iAppId, it.strDeviceName, it.strDeviceTypeInfo, it.iLoginTime, - it.iLoginPlatform, it.strLoginLocation - ) - }, echo) - } - - @Serializable - data class DevInfo( - @SerialName("app_id") val appId: Long, - @SerialName("device_name") val deviceName: String?, - @SerialName("device_kind") val deviceType: String?, - @SerialName("login_time") val loginTime: Long?, - @SerialName("login_platform") val loginPlatform: Long?, - @SerialName("location") val location: String?, - @SerialName("guid") val guid: String? = "" - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetProfileCard.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetProfileCard.kt deleted file mode 100644 index 053040a3..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetProfileCard.kt +++ /dev/null @@ -1,78 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import com.tencent.mobileqq.data.Card -import moe.fuqiuluo.qqinterface.servlet.CardSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.structures.Status -import moe.fuqiuluo.shamrock.remote.structures.resultToString -import moe.fuqiuluo.shamrock.remote.service.data.VipInfo -import moe.fuqiuluo.shamrock.remote.service.data.VipType -import moe.fuqiuluo.shamrock.remote.service.data.profile.Location -import moe.fuqiuluo.shamrock.remote.service.data.profile.ProfileCard -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_user_info", ["get_profile_card"]) -internal object GetProfileCard: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val uin = session.getLong("user_id") - val refresh = session.getBooleanOrDefault("refresh", session.getBooleanOrDefault("no_cache", false)) - - var card: Card? = CardSvc.getProfileCard(uin).getOrNull() - if (refresh || !card.ok()) { - card = CardSvc.refreshAndGetProfileCard(uin).getOrNull() - } - if (!card.ok()) { - return logic("get profilecard error, please check your user_id or network", session.echo) - } - requireNotNull(card) - - return resultToString(true, Status.Ok, ProfileCard( - uin = card.uin.toLong(), - name = card.strNick, - mail = card.strShowName ?: card.strEmail ?: "", - remark = card.strReMark.let { if (it.isNullOrEmpty()) card.strAutoRemark else it }, - findMethod = card.addSrcName, - displayName = card.strContactName, - maxVoteCnt = card.bAvailVoteCnt, - haveVoteCnt = card.bHaveVotedCnt, - vipList = arrayListOf().apply { - if (card.bQQVipOpen == 1.toByte()) { - add(VipInfo(VipType.QQ_VIP, card.iQQVipLevel, card.iQQVipType, 0)) - } - if (card.bSuperQQOpen == 1.toByte()) { - add(VipInfo(VipType.SUPER_QQ, card.iSuperQQLevel, card.iSuperQQType, 0)) - } - if (card.bSuperVipOpen == 1.toByte()) { - add(VipInfo(VipType.SUPER_VIP, card.iSuperVipLevel, card.iSuperVipType, card.lSuperVipTemplateId)) - } - if (card.bHollywoodVipOpen == 1.toByte()) { - add(VipInfo(VipType.QQ_VIDEO, card.iHollywoodVipLevel, card.iHollywoodVipType, 0)) - } - if (card.bBigClubVipOpen == 1.toByte()) { - add(VipInfo(VipType.BIG_VIP, card.iBigClubVipLevel, card.iBigClubVipType, card.lBigClubTemplateId)) - } - if (card.isYellowDiamond || card.isSuperYellowDiamond) { - add(VipInfo(VipType.YELLOW_VIP, card.yellowLevel, 0, 0)) - } - }, - hobbyEntry = card.hobbyEntry, - level = card.iQQLevel, - birthday = card.lBirthday, - loginDay = card.lLoginDays, - voteCnt = card.lVoteCount, - qid = card.qid, - schoolVerified = card.schoolVerifiedFlag, - location = Location( - card.strCity, card.strCompany, card.strCountry, card.strProvince, card.strHometownDesc, card.strSchool - ), - cookie = card.vCookies - ), echo = session.echo) - } - - override val requiredParams: Array = arrayOf("user_id") - - private fun Card?.ok(): Boolean { - return this != null && !strNick.isNullOrBlank() - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetProhibitedMemberList.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetProhibitedMemberList.kt deleted file mode 100644 index 55baff1e..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetProhibitedMemberList.kt +++ /dev/null @@ -1,29 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_prohibited_member_list") -internal object GetProhibitedMemberList: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupCode = session.getLong("group_id") - return invoke(groupCode, session.echo) - } - - suspend operator fun invoke( - groupCode: Long, - echo: JsonElement = EmptyJsonString - ): String { - val result = GroupSvc.getProhibitedMemberList(groupCode) - if (result.isFailure) { - return error(result.exceptionOrNull()?.message ?: "获取禁言列表失败", echo, arrayResult = true) - } - return ok(result.getOrThrow(), echo) - } - - override val requiredParams: Array = arrayOf("group_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetRecord.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetRecord.kt deleted file mode 100644 index 47ace91f..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetRecord.kt +++ /dev/null @@ -1,41 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.shamrock.helper.LocalCacheHelper -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.service.data.OutResource -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.utils.AudioUtils -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_record") internal object GetRecord : IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val file = session.getString("file") - .replace(regex = "[{}\\-]".toRegex(), replacement = "") - .replace(" ", "") - .split(".")[0].lowercase() - val format = session.getString("out_format") - return invoke(file, format, session.echo) - } - - operator fun invoke(file: String, format: String, echo: JsonElement = EmptyJsonString): String { - val pttFile = LocalCacheHelper.getCachePttFile(file) - return if (pttFile.exists()) { - val isSilk = AudioUtils.isSilk(pttFile) - val audioFile = when (format) { - "amr" -> AudioUtils.audioToAmr(pttFile, isSilk) - else -> AudioUtils.audioToFormat(pttFile, isSilk, format) - } - ok( - OutResource( - audioFile.toString(), url = "/res/${audioFile.nameWithoutExtension}", md5 = audioFile.nameWithoutExtension - ), echo - ) - } else { - error("not found record file from cache", echo) - } - } - - override val requiredParams: Array = arrayOf("file", "out_format") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetSelfInfo.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetSelfInfo.kt deleted file mode 100644 index bea66cc2..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetSelfInfo.kt +++ /dev/null @@ -1,24 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import com.tencent.mobileqq.app.QQAppInterface -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.structures.Status -import moe.fuqiuluo.shamrock.remote.structures.resultToString -import moe.fuqiuluo.shamrock.remote.service.data.UserDetail -import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_self_info") -internal object GetSelfInfo: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - //val accounts = MobileQQ.getMobileQQ().allAccounts - val runtime = AppRuntimeFetcher.appRuntime as QQAppInterface - val curUin = runtime.currentAccountUin - //val account = accounts.firstOrNull { it.uin == curUin } - - return resultToString(true, Status.Ok, UserDetail( - curUin.toLong(), runtime.currentNickname, runtime.currentNickname - ), echo = session.echo) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetStatus.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetStatus.kt deleted file mode 100644 index b552483e..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetStatus.kt +++ /dev/null @@ -1,23 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.structures.Status -import moe.fuqiuluo.shamrock.remote.structures.resultToString -import moe.fuqiuluo.shamrock.remote.service.data.BotStatus -import moe.fuqiuluo.shamrock.remote.service.data.Self -import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_status", ["status"]) -internal object GetStatus: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val runtime = AppRuntimeFetcher.appRuntime - val curUin = runtime.currentAccountUin - return resultToString(true, Status.Ok, listOf( - BotStatus( - Self("qq", curUin.toLong()), runtime.isLogin, status = "正常", good = runtime.isLogin - ) - ), echo = session.echo) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetStrangerInfo.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetStrangerInfo.kt deleted file mode 100644 index f4d07d8e..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetStrangerInfo.kt +++ /dev/null @@ -1,160 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import com.tencent.mobileqq.data.Card -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.toHexString -import moe.fuqiuluo.qqinterface.servlet.CardSvc -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_stranger_info", ["_get_stranger_info"]) -internal object GetStrangerInfo: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val userId = session.getLong("user_id") - return invoke(userId, session.echo) - } - - suspend operator fun invoke(userId: Long, echo: JsonElement = EmptyJsonString): String { - val info = CardSvc.refreshAndGetProfileCard(userId).onFailure { - return logic("unable to fetch stranger info", echo) - }.getOrThrow() - - return ok(info.run { - StrangerInfo( - uid = userId, - nickname = strNick, - age = age, - sex = when(shGender) { - Card.FEMALE -> "female" - Card.MALE -> "male" - else -> "unknown" - }, - level = iQQLevel, - loginDays = lLoginDays, - qid = qid ?: "", - vote = lVoteCount, - wzryHonor = wzryHonorInfo?.toHexString(), - ext = StrangerInfoExt( - addSrcId, addSrcName, addSubSrcId, - - allowCalInteractive, allowClick, allowPeopleSee, authState, - - bBigClubVipOpen, bHollywoodVipOpen, bQQVipOpen, bSuperQQOpen, bSuperVipOpen, - - bVoted, babyQSwitch, bindPhoneInfo, cardId, cardType, category, clothesId, - - coverUrl, declaration, defaultCardId, diyComplicatedInfo, diyDefaultText, - - diyText, diyTextDegree, diyTextFontId, diyTextHeight, diyTextWidth, - - diyTextLocX, diyTextLocY, dressUpIsOn, encId, enlargeQzonePic, - - extendFriendEntryAddFriend, extendFriendEntryContact, extendFriendFlag, - - extendFriendQuestion, extendFriendVoiceDuration, favoriteSource, feedPreviewTime, - - fontId, fontType, qidBgUrl, qidColor, qidLogoUrl, qqCardIsOn, schoolId, - - schoolName, schoolVerifiedFlag, showPublishButton, singer, songDuration, - - songId, songName - ) - ) - }, echo) - } - - override val requiredParams: Array = arrayOf("user_id") - - @Serializable - data class StrangerInfo( - @SerialName("user_id") val uid: Long, - @SerialName("nickname") val nickname: String, - @SerialName("age") val age: Byte, - @SerialName("sex") val sex: String, - @SerialName("level") val level: Int, - @SerialName("login_days") val loginDays: Long, - @SerialName("qid") val qid: String?, - @SerialName("vote") val vote: Long, - @SerialName("wzry_honor") val wzryHonor: String?, - @SerialName("ext") val ext: StrangerInfoExt - ) - - @Serializable - data class StrangerInfoExt( - @SerialName("add_src_id") val addSrcId: Long, - @SerialName("add_src_name") val addSrcName: String?, - @SerialName("add_sub_src_id") val addSubSrcId: Long, - - @SerialName("allow_cal_interactive") val allowCalInteractive: Boolean, - @SerialName("allow_click") val allowClick: Boolean, - @SerialName("allow_people_see") val allowPeopleSee: Boolean, - @SerialName("auth_state") val authState: Long, - - @SerialName("big_club_vip_open") val bBigClubVipOpen: Byte, - @SerialName("hollywood_vip_open") val bHollywoodVipOpen: Byte, - @SerialName("qq_vip_open") val bQQVipOpen: Byte, - @SerialName("super_qq_open") val bSuperQQOpen: Byte, - @SerialName("super_vip_open") val bSuperVipOpen: Byte, - - @SerialName("voted") val bVoted: Byte, - @SerialName("baby_q_switch") val babyQSwitch: Boolean, - - @SerialName("bind_phone_info") val bindPhoneInfo: String?, - - @SerialName("card_id") val cardId: Long, - @SerialName("card_type") val cardType: Int, - @SerialName("category") val category: Int, - @SerialName("clothes_id") val clothesId: Int, - - @SerialName("cover_url") val coverUrl: String?, - @SerialName("declaration") val declaration: String?, - @SerialName("default_card_id") val defaultCardId: Int, - - @SerialName("diy_complicated_info") val diyComplicatedInfo: String?, - @SerialName("diy_default_text") val diyDefaultText: String?, - @SerialName("diy_text") val diyText: String?, - @SerialName("diy_text_degree") val diyTextDegree: Float, - @SerialName("diy_text_font_id") val diyTextFontId: Int, - @SerialName("diy_text_height") val diyTextHeight: Float, - @SerialName("diy_text_width") val diyTextWidth: Float, - @SerialName("diy_text_loc_x") val diyTextLocX: Float, - @SerialName("diy_text_loc_y") val diyTextLocY: Float, - - @SerialName("dress_up_is_on") val dressUpIsOn: Boolean, - @SerialName("enc_id") val encId: String?, - @SerialName("enlarge_qzone_pic") val enlargeQzonePic: Int, - @SerialName("extend_friend_entry_add_friend") val extendFriendEntryAddFriend: Short, - @SerialName("extend_friend_entry_contact") val extendFriendEntryContact: Short, - @SerialName("extend_friend_flag") val extendFriendFlag: Int, - @SerialName("extend_friend_question") val extendFriendQuestion: Short, - @SerialName("extend_friend_voice_duration") val extendFriendVoiceDuration: Int, - @SerialName("favorite_source") val favoriteSource: Int, - @SerialName("feed_preview_time") val feedPreviewTime: Long, - @SerialName("font_id") val fontId: Int, - @SerialName("font_type") val fontType: Int, - - @SerialName("qid_bg_url") val qidBgUrl: String?, - @SerialName("qid_color") val qidColor: String?, - @SerialName("qid_logo_url") val qidLogoUrl: String?, - - @SerialName("qq_card_is_on") val qqCardIsOn: Boolean, - - @SerialName("school_id") val schoolId: String?, - @SerialName("school_name") val schoolName: String?, - @SerialName("school_verified_flag") val schoolVerifiedFlag: Boolean, - - @SerialName("show_publish_button") val showPublishButton: Boolean, - - @SerialName("singer") val singer: String?, - @SerialName("song_dura") val songDuration: Long, - - @SerialName("song_id") val songId: String?, - @SerialName("song_name") val songName: String?, - - - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetSupportedActions.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetSupportedActions.kt deleted file mode 100644 index bd5c9c77..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetSupportedActions.kt +++ /dev/null @@ -1,15 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import moe.fuqiuluo.shamrock.remote.action.ActionManager -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.structures.Status -import moe.fuqiuluo.shamrock.remote.structures.resultToString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_supported_actions") -internal object GetSupportedActions: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - return resultToString(true, Status.Ok, ActionManager.actionMap.keys.toList(), echo = session.echo) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetTroopHonor.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetTroopHonor.kt deleted file mode 100644 index c3d6ae3b..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetTroopHonor.kt +++ /dev/null @@ -1,58 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.shamrock.helper.TroopHonorHelper.decodeHonor -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.service.data.GroupAllHonor -import moe.fuqiuluo.shamrock.remote.service.data.GroupMemberHonor -import moe.fuqiuluo.shamrock.remote.service.data.HONOR_GROUP_FIRE -import moe.fuqiuluo.shamrock.remote.service.data.HONOR_GROUP_FLAME -import moe.fuqiuluo.shamrock.remote.service.data.HONOR_HAPPY -import moe.fuqiuluo.shamrock.remote.service.data.HONOR_NEWBIE -import moe.fuqiuluo.shamrock.remote.service.data.HONOR_TALKATIVE -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_group_honor_info", ["get_troop_honor_info"]) -internal object GetTroopHonor: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - val refresh = session.getBooleanOrDefault("refresh", session.getBooleanOrDefault("no_cache", false)) - return invoke(groupId, refresh, session.echo) - } - - suspend operator fun invoke(groupId: Long, refresh: Boolean, echo: JsonElement = EmptyJsonString): String { - val honorInfo = ArrayList() - - GroupSvc.getGroupMemberList(groupId, refresh).onFailure { - return error(it.message ?: "unknown error", echo) - }.onSuccess { memberList -> - memberList.forEach { member -> - GroupSvc.parseHonor(member.honorList).forEach { - val honor = decodeHonor(member.memberuin.toLong(), it, member.mHonorRichFlag) - if (honor != null) { - honor.nick = member.troopnick.ifEmpty { member.friendnick } - honorInfo.add(honor) - } - } - } - } - - return ok(GroupAllHonor( - groupId = groupId, - currentTalkActive = honorInfo.firstOrNull { - it.id == HONOR_TALKATIVE - }, - talkativeList = honorInfo.filter { it.id == HONOR_TALKATIVE }, - performerList = honorInfo.filter { it.id == HONOR_GROUP_FIRE }, - legendList = honorInfo.filter { it.id == HONOR_GROUP_FLAME }, - strongNewbieList = honorInfo.filter { it.id == HONOR_NEWBIE }, - emotionList = honorInfo.filter { it.id == HONOR_HAPPY }, - all = honorInfo - ), echo) - } - - override val requiredParams: Array = arrayOf("group_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetTroopInfo.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetTroopInfo.kt deleted file mode 100644 index 6bbcde5b..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetTroopInfo.kt +++ /dev/null @@ -1,41 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.service.data.SimpleTroopInfo -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_group_info") -internal object GetTroopInfo: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - val refresh = session.getBooleanOrDefault("refresh", session.getBooleanOrDefault("no_cache", false)) - return invoke(groupId, refresh, session.echo) - } - - suspend operator fun invoke(groupId: Long, refresh: Boolean, echo: JsonElement = EmptyJsonString): String { - val groupInfo = GroupSvc.getGroupInfo(groupId, refresh).getOrNull() - return if ( groupInfo == null || groupInfo.troopuin.isNullOrBlank()) { - logic("Unable to obtain group information", echo) - } else { - ok(SimpleTroopInfo( - groupId = groupInfo.troopuin.toLong(), - groupUin = groupInfo.troopcode.toLong(), - groupName = groupInfo.troopname ?: groupInfo.newTroopName ?: groupInfo.oldTroopName, - groupRemark = groupInfo.troopRemark, - adminList = GroupSvc.getAdminList(groupId, true), - classText = groupInfo.mGroupClassExtText, - isFrozen = groupInfo.mIsFreezed != 0, - maxMember = groupInfo.wMemberMax, - memNum = groupInfo.wMemberNum, - memCount = groupInfo.wMemberNum, - maxNum = groupInfo.wMemberMax, - ), echo) - } - } - - override val requiredParams: Array = arrayOf("group_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetTroopList.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetTroopList.kt deleted file mode 100644 index 3ad007b5..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetTroopList.kt +++ /dev/null @@ -1,46 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.service.data.SimpleTroopInfo -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_group_list") -internal object GetTroopList : IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val refresh = session.getBooleanOrDefault("refresh", session.getBooleanOrDefault("no_cache", true)) - return invoke(refresh, session.echo) - } - - suspend operator fun invoke(refresh: Boolean, echo: JsonElement = EmptyJsonString): String { - val troopList = arrayListOf() - GroupSvc.getGroupList(refresh).onFailure { - return error(it.message ?: "unknown error", echo, arrayResult = true) - }.onSuccess { troops -> - troops.forEach { groupInfo -> - if (groupInfo.troopcode.isNullOrEmpty()) return@forEach - - troopList.add( - SimpleTroopInfo( - groupId = groupInfo.troopuin.toLong(), - groupUin = groupInfo.troopcode.toLong(), - groupName = groupInfo.troopname ?: groupInfo.newTroopName - ?: groupInfo.oldTroopName, - groupRemark = groupInfo.troopRemark, - adminList = GroupSvc.getAdminList(groupInfo.troopuin.toLong(), true), - classText = groupInfo.mGroupClassExtText, - isFrozen = groupInfo.mIsFreezed != 0, - maxMember = groupInfo.wMemberMax, - memNum = groupInfo.wMemberNum, - memCount = groupInfo.wMemberNum, - maxNum = groupInfo.wMemberMax - ) - ) - } - } - return ok(troopList, echo) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetTroopMemberInfo.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetTroopMemberInfo.kt deleted file mode 100644 index 787c46e6..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetTroopMemberInfo.kt +++ /dev/null @@ -1,67 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import com.tencent.mobileqq.data.Card -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.service.data.SimpleTroopMemberInfo -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_group_member_info") -internal object GetTroopMemberInfo : IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val userId = session.getLong("user_id") - val groupId = session.getLong("group_id") - val refresh = session.getBooleanOrDefault("refresh", session.getBooleanOrDefault("no_cache", false)) - - return invoke(groupId, userId, refresh, session.echo) - } - - suspend operator fun invoke( - groupId: Long, - userId: Long, - refresh: Boolean, - echo: JsonElement = EmptyJsonString - ): String { - val info = GroupSvc.getTroopMemberInfoByUin(groupId, userId, refresh).onFailure { - return error(it.message ?: "unknown error", echo) - }.getOrThrow() - - return ok( - SimpleTroopMemberInfo( - uin = info.memberuin.toLong(), - name = info.friendnick.ifNullOrEmpty(info.autoremark) ?: "", - showName = info.troopnick.ifNullOrEmpty(info.troopColorNick), - cardName = info.troopnick.ifNullOrEmpty(info.troopColorNick), - distance = info.distance, - honor = GroupSvc.parseHonor(info.honorList), - joinTime = info.join_time, - lastActiveTime = info.last_active_time, - uniqueName = info.mUniqueTitle, - groupId = groupId, - nick = info.friendnick.ifNullOrEmpty(info.autoremark) ?: "", - sex = when (info.sex.toShort()) { - Card.FEMALE -> "female" - Card.MALE -> "male" - else -> "unknown" - }, - area = info.alias ?: "", - lastSentTime = info.last_active_time, - level = info.level, - role = GroupSvc.getMemberRole(groupId, userId), - unfriendly = false, - title = info.mUniqueTitle ?: "", - titleExpireTime = info.mUniqueTitleExpire, - cardChangeable = GroupSvc.isAdmin(groupId), - age = info.age.toInt(), - shutUpTimestamp = 0L - ), echo - ) - } - - override val requiredParams: Array = arrayOf("user_id", "group_id") -} - diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetTroopMemberList.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetTroopMemberList.kt deleted file mode 100644 index 21d8fa0a..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetTroopMemberList.kt +++ /dev/null @@ -1,79 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import com.tencent.mobileqq.data.Card -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.service.data.SimpleTroopMemberInfo -import moe.fuqiuluo.shamrock.remote.service.data.push.MemberRole -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_group_member_list") -internal object GetTroopMemberList : IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - val refresh = session.getBooleanOrDefault("refresh", session.getBooleanOrDefault("no_cache", false)) - return invoke(groupId, refresh, session.echo) - } - - suspend operator fun invoke( - groupId: Long, - refresh: Boolean, - echo: JsonElement = EmptyJsonString - ): String { - val memberList = GroupSvc.getGroupMemberList(groupId, refresh).onFailure { - return error(it.message ?: "unknown error", echo, arrayResult = true) - }.getOrThrow() - val prohibitedMemberList = GroupSvc.getProhibitedMemberList(groupId) - .getOrDefault(arrayListOf()) - .associate { it.memberUin to it.shutuptimestap.toLong() } - return ok(arrayListOf().apply { - memberList.forEach { info -> - if (info.memberuin != "0") { - add( - SimpleTroopMemberInfo( - uin = info.memberuin.toLong(), - name = info.friendnick.ifNullOrEmpty(info.autoremark) ?: "", - showName = info.troopnick.ifNullOrEmpty(info.troopColorNick), - cardName = info.troopnick.ifNullOrEmpty(info.troopColorNick), - distance = info.distance, - honor = GroupSvc.parseHonor(info.honorList), - joinTime = info.join_time, - lastActiveTime = info.last_active_time, - uniqueName = info.mUniqueTitle, - groupId = groupId, - nick = info.friendnick.ifNullOrEmpty(info.autoremark) ?: "", - sex = when (info.sex.toShort()) { - Card.FEMALE -> "female" - Card.MALE -> "male" - else -> "unknown" - }, - area = info.alias ?: "", - lastSentTime = info.last_active_time, - level = info.level, - role = GroupSvc.getMemberRole(groupId, info.memberuin.toLong()) - /*when { - GroupSvc.getOwner(groupId) - .toString() == info.memberuin -> MemberRole.Owner - info.memberuin.toLong() in GroupSvc.getAdminList(groupId) -> MemberRole.Admin - else -> MemberRole.Member - }*/, - unfriendly = false, - title = info.mUniqueTitle ?: "", - titleExpireTime = info.mUniqueTitleExpire, - cardChangeable = GroupSvc.isAdmin(groupId), - age = 0, - shutUpTimestamp = prohibitedMemberList[info.memberuin.toLong()] ?: 0L - ) - ) - } - } - }, echo) - } - - override val requiredParams: Array = arrayOf("group_id") -} - diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetUid.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetUid.kt deleted file mode 100644 index eb2ddefc..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetUid.kt +++ /dev/null @@ -1,29 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.coroutines.suspendCancellableCoroutine -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.structures.Status -import moe.fuqiuluo.shamrock.remote.structures.resultToString -import moe.fuqiuluo.shamrock.tools.asString -import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher -import moe.fuqiuluo.symbols.OneBotHandler -import kotlin.coroutines.resume - -@OneBotHandler("get_uid") -internal object GetUid: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val kernelService = NTServiceFetcher.kernelService - val sessionService = kernelService.wrapperSession - val uinList = session.getArray("uin_list").map { - it.asString.toLong() - } - - val uidMap = suspendCancellableCoroutine { continuation -> - sessionService.uixConvertService.getUid(uinList.toHashSet()) { - continuation.resume(it) - } - } - return resultToString(true, Status.Ok, uidMap, echo = session.echo) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetUinByUid.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetUinByUid.kt deleted file mode 100644 index 5bdaf3b7..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetUinByUid.kt +++ /dev/null @@ -1,28 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.coroutines.suspendCancellableCoroutine -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.structures.Status -import moe.fuqiuluo.shamrock.remote.structures.resultToString -import moe.fuqiuluo.shamrock.tools.asString -import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher -import moe.fuqiuluo.symbols.OneBotHandler -import kotlin.coroutines.resume - -@OneBotHandler("get_uin_by_uid") -internal object GetUinByUid: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val kernelService = NTServiceFetcher.kernelService - val sessionService = kernelService.wrapperSession - val uidList = session.getArray("uid_list").map { - it.asString - } - val uinMap = suspendCancellableCoroutine { continuation -> - sessionService.uixConvertService.getUin(uidList.toHashSet()) { - continuation.resume(it) - } - } - return resultToString(true, Status.Ok, uinMap, echo = session.echo) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetVersionInfo.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetVersionInfo.kt deleted file mode 100644 index 6c264db0..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetVersionInfo.kt +++ /dev/null @@ -1,30 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.service.data.VersionInfo -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.tools.ShamrockVersion -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_version_info", ["get_version"]) -internal object GetVersionInfo : IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - return invoke(session.echo) - } - - operator fun invoke(echo: JsonElement = EmptyJsonString): String { - return ok( - VersionInfo( - appFullName = "Shamrock v$ShamrockVersion", - appName = "Shamrock", - appVersion = ShamrockVersion, - impl = "shamrock", - version = ShamrockVersion, - onebotVersion = "11", - ), - echo = echo - ) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetWeather.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetWeather.kt deleted file mode 100644 index 0bd1f466..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetWeather.kt +++ /dev/null @@ -1,36 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.ark.WeatherSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_weather") -internal object GetWeather: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - session.getIntOrNull("code")?.let { - return invoke(it, session.echo) - } - session.getString("city").let { - return invoke(it, session.echo) - } - } - - suspend operator fun invoke(code: Int, echo: JsonElement = EmptyJsonString): String { - val result = WeatherSvc.fetchWeatherCard(code) - if (result.isFailure) { - return error("fetch weather failed", echo) - } - return ok(result.getOrThrow(), echo) - } - - suspend operator fun invoke(city: String, echo: JsonElement = EmptyJsonString): String { - val code = WeatherSvc.searchCity(city) - if (code.isFailure || code.getOrThrow().isEmpty()) { - return error("search city failed", echo) - } - return invoke(code.getOrThrow().first().adcode, echo) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetWeatherCityCode.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetWeatherCityCode.kt deleted file mode 100644 index 93535711..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetWeatherCityCode.kt +++ /dev/null @@ -1,29 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.ark.WeatherSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("get_weather_city_code") -internal object GetWeatherCityCode: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val city = session.getString("city") - return invoke(city, session.echo) - } - - suspend operator fun invoke(city: String, echo: JsonElement = EmptyJsonString): String { - val result = WeatherSvc.searchCity(city) - if (result.isFailure) { - return error(result.exceptionOrNull()?.message ?: "unknown error", echo) - } - - val regions = result.getOrThrow() - - return ok(regions, echo) - } - - override val requiredParams: Array = arrayOf("city") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GroupPoke.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GroupPoke.kt deleted file mode 100644 index 6c8a1d3f..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GroupPoke.kt +++ /dev/null @@ -1,24 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("poke") -internal object GroupPoke: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - val userId = session.getLong("user_id") - return invoke(groupId, userId, session.echo) - } - - operator fun invoke(groupId: Long, userId: Long, echo: JsonElement = EmptyJsonString): String { - GroupSvc.poke(groupId, userId) - return ok("成功", echo) - } - - override val requiredParams: Array = arrayOf("group_id", "user_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/IsBlackListUin.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/IsBlackListUin.kt deleted file mode 100644 index e43ccc98..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/IsBlackListUin.kt +++ /dev/null @@ -1,41 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import com.tencent.mobileqq.profilecard.api.IProfileCardBlacklistApi -import com.tencent.mobileqq.qroute.QRoute -import kotlinx.coroutines.withTimeoutOrNull -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine - -@OneBotHandler("is_blacklist_uin") -internal object IsBlackListUin: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val userId = session.getLong("user_id") - return invoke(userId, session.echo) - } - - suspend operator fun invoke(uin: Long, echo: JsonElement = EmptyJsonString): String { - val blacklistApi = QRoute.api(IProfileCardBlacklistApi::class.java) - val isBlack = withTimeoutOrNull(5000) { - suspendCoroutine { continuation -> - blacklistApi.isBlackOrBlackedUin(uin.toString()) { - continuation.resume(it) - } - } - } ?: false - return ok(data = IsBlackListUinResult(isBlack), echo) - } - - override val requiredParams: Array = arrayOf("user_id") - - @Serializable - data class IsBlackListUinResult( - @SerialName("is") val isBlack: Boolean - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/KickTroopMember.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/KickTroopMember.kt deleted file mode 100644 index 1cf22812..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/KickTroopMember.kt +++ /dev/null @@ -1,27 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("set_group_kick", ["kick_group_member"]) -internal object KickTroopMember: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - val userId = session.getLong("user_id") - val kickMsg = session.getStringOrNull("kick_msg") ?: session.getStringOrNull("kick_message") ?: "" - val rejectAddRequest = session.getBooleanOrDefault("reject_add_request", false) - - return invoke(groupId, userId, rejectAddRequest, kickMsg, session.echo) - } - - operator fun invoke(groupId: Long, userId: Long, rejectAddRequest: Boolean = false, kickMsg: String, echo: JsonElement = EmptyJsonString): String { - GroupSvc.kickMember(groupId, rejectAddRequest, kickMsg, userId) - return ok("成功", echo) - } - - override val requiredParams: Array = arrayOf("group_id", "user_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/LeaveTroop.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/LeaveTroop.kt deleted file mode 100644 index 0d49d2b7..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/LeaveTroop.kt +++ /dev/null @@ -1,26 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("leave_group", ["set_group_leave"]) -internal object LeaveTroop: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - return invoke(groupId, session.echo) - } - - operator fun invoke(groupId: Long, echo: JsonElement = EmptyJsonString): String { - if (GroupSvc.isOwner(groupId)) { - return error("you are the owner of this group", echo) - } - GroupSvc.resignTroop(groupId) - return ok("成功", echo) - } - - override val requiredParams: Array = arrayOf("group_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/ModifyTroopMemberName.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/ModifyTroopMemberName.kt deleted file mode 100644 index 0cf477df..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/ModifyTroopMemberName.kt +++ /dev/null @@ -1,30 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.qqinterface.servlet.TicketSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("set_group_card") -internal object ModifyTroopMemberName: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - val userId = session.getLong("user_id") - val name = session.getStringOrNull("card") ?: "" - return invoke(groupId, userId, name, session.echo) - } - - operator fun invoke(groupId: Long, userId: Long, card: String, echo: JsonElement = EmptyJsonString): String { - if (!GroupSvc.isAdmin(groupId) && userId != TicketSvc.getUin().toLong()) { - return logic("you are not admin", echo) - } - return if(GroupSvc.modifyGroupMemberCard(groupId, userId, card)) - ok("成功", echo) - else error("check if member or group exist", echo) - } - - override val requiredParams: Array = arrayOf("group_id", "user_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/ModifyTroopName.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/ModifyTroopName.kt deleted file mode 100644 index dd44a05a..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/ModifyTroopName.kt +++ /dev/null @@ -1,29 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("set_group_name") -internal object ModifyTroopName: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - val groupName = session.getString("group_name") - - return invoke(groupId, groupName, session.echo) - } - - operator fun invoke(groupId: Long, name: String, echo: JsonElement = EmptyJsonString): String { - return if (GroupSvc.isAdmin(groupId)) { - GroupSvc.modifyTroopName(groupId, name) - ok("成功", echo) - } else { - logic("You are not the administrator of the group", echo) - } - } - - override val requiredParams: Array = arrayOf("group_id", "group_name") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/ModifyTroopRemark.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/ModifyTroopRemark.kt deleted file mode 100644 index c816c098..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/ModifyTroopRemark.kt +++ /dev/null @@ -1,25 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("set_group_remark", ["modify_group_remark"]) -internal object ModifyTroopRemark: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - val remark = session.getStringOrNull("remark") ?: "" - return invoke(groupId, remark, session.echo) - } - - operator fun invoke(groupId: Long, remark: String, echo: JsonElement = EmptyJsonString): String { - return if(GroupSvc.modifyGroupRemark(groupId, remark)) - ok("成功", echo) - else error("check if member or group exist", echo) - } - - override val requiredParams: Array = arrayOf("group_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/QuickOperation.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/QuickOperation.kt deleted file mode 100644 index bbda48f9..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/QuickOperation.kt +++ /dev/null @@ -1,154 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import com.tencent.qqnt.kernel.nativeinterface.MsgRecord -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonPrimitive -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.qqinterface.servlet.MsgSvc -import moe.fuqiuluo.qqinterface.servlet.TicketSvc -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.asBooleanOrNull -import moe.fuqiuluo.shamrock.tools.asInt -import moe.fuqiuluo.shamrock.tools.asIntOrNull -import moe.fuqiuluo.shamrock.tools.asJsonObject -import moe.fuqiuluo.shamrock.tools.asString -import moe.fuqiuluo.shamrock.tools.json -import moe.fuqiuluo.shamrock.tools.jsonArray -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler(".handle_quick_operation_async") -internal object QuickOperation : IActionHandler() { - val actionMsgTypes = arrayOf( - "record", "voice", "video", "markdown" - ) - - override suspend fun internalHandle(session: ActionSession): String { - val botId = session.getLong("self_id") - if (botId != TicketSvc.getLongUin()) { - return logic("当前登录账号和输入的`self_id`不一致", session.echo) - } - val context = session.getObject("context") - //val msgType = context["message_type"].asString - val msgHash = context["message_id"].asInt - //val peerId = context[when(msgType) { - // "group" -> "group_id" - // "private" -> "user_id" - // else -> error("unknown message type: $msgType") - //}].asLong - val record = MsgSvc.getMsg(msgHash).getOrNull() - ?: return logic("获取源消息失败", session.echo) - - val operation = session.getObject("operation") - - if (operation.containsKey("reply")) { - LogCenter.log({ "websocket quickly reply successfully" }, Level.DEBUG) - val autoEscape = operation["auto_escape"].asBooleanOrNull - val atSender = operation["at_sender"].asBooleanOrNull ?: false - val autoReply = operation["auto_reply"].asBooleanOrNull ?: true - val message = operation["reply"] - if (message is JsonPrimitive) { - quicklyReply( - record, - if (autoEscape == true) - listOf( - mapOf( - "type" to "text", - "data" to mapOf( - "text" to message.asString - ) - ) - ).json - else MessageHelper.decodeCQCode(message.asString), - msgHash, - atSender, - autoReply - ) - } else if (message is JsonArray) { - quicklyReply( - record, - message, - msgHash, - atSender, - autoReply - ) - } - } - - if (MsgConstant.KCHATTYPEGROUP == record.chatType) { - if (operation["delete"].asBooleanOrNull == true) { - val duration = operation["delay"].asIntOrNull - if (duration != null) { - GlobalScope.launch { - delay(duration.toLong()) - MsgSvc.recallMsg(msgHash) - } - } else { - MsgSvc.recallMsg(msgHash) - } - } - if (operation["kick"].asBooleanOrNull == true) { - GroupSvc.kickMember(record.peerUin, false, "", record.senderUin) - } - if (operation["ban"].asBooleanOrNull == true) { - val banTime = operation["ban_duration"].asIntOrNull ?: (30 * 60) - if (banTime <= 0) return logic("禁言时间必须大于0", session.echo) - GroupSvc.banMember(record.peerUin, record.senderUin, banTime) - } - } - - - return ok("操作成功", session.echo) - } - - override val requiredParams: Array = arrayOf("context", "operation", "self_id") - - suspend fun quicklyReply( - record: MsgRecord, - message: JsonArray, - msgHash: Int, - atSender: Boolean, - autoReply: Boolean - ) { - val messageList = mutableListOf() - message.filter { - it.asJsonObject["type"]?.asString in actionMsgTypes - }.let { - if (it.isNotEmpty()) { - it.map { listOf(it) }.forEach { - MsgSvc.sendToAio(record.chatType, record.peerUin.toString(), it.jsonArray, retryCnt = 3) - } - return - } - } - - if (autoReply) messageList.add( - mapOf( - "type" to "reply", - "data" to mapOf( - "id" to msgHash - ) - ).json - ) // 添加回复 - if (MsgConstant.KCHATTYPEGROUP == record.chatType && atSender) { - messageList.add( - mapOf( - "type" to "at", - "data" to mapOf( - "qq" to record.senderUin - ) - ).json - ) // 添加@发送者 - } - messageList.addAll(message) - MsgSvc.sendToAio(record.chatType, record.peerUin.toString(), JsonArray(messageList), retryCnt = 3) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/RenameGroupFolder.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/RenameGroupFolder.kt deleted file mode 100644 index 2f902d1a..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/RenameGroupFolder.kt +++ /dev/null @@ -1,27 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.FileSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("rename_group_folder") -internal object RenameGroupFolder: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - val folderId = session.getString("folder_id") - val name = session.getString("name") - return invoke(groupId, folderId, name, session.echo) - } - - suspend operator fun invoke(groupId: Long, folderId: String, name: String, echo: JsonElement = EmptyJsonString): String { - if (!FileSvc.renameFolder(groupId, folderId, name)) { - return error("rename folder failed", echo = echo) - } - return ok("success", echo = echo) - } - - override val requiredParams: Array = arrayOf("group_id", "folder_id", "name") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/RequestUploadGroupImage.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/RequestUploadGroupImage.kt deleted file mode 100644 index 0952a6a6..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/RequestUploadGroupImage.kt +++ /dev/null @@ -1,29 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import moe.fuqiuluo.qqinterface.servlet.transfile.NtV2RichMediaSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("request_upload_group_image") -internal object RequestUploadGroupImage: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val md5 = session.getString("md5").uppercase() - val fileSize = session.getLong("file_size") - val width = session.getInt("width") - val height = session.getInt("height") - val groupId = session.getString("group_id") - NtV2RichMediaSvc.requestUploadGroupPic( - groupId.toULong(), - md5, - fileSize.toULong(), - width.toUInt(), - height.toUInt() - ).onSuccess { - return ok(it, session.echo) - }.onFailure { - return error(it.message ?: it.toString(), session.echo) - } - return logic("request_upload_group_image failed", session.echo) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/RestartMe.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/RestartMe.kt deleted file mode 100644 index 500bcfaa..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/RestartMe.kt +++ /dev/null @@ -1,18 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("restart_me") -internal object RestartMe: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - return invoke(2000, session.echo) - } - - operator fun invoke(duration: Int, echo: JsonElement = EmptyJsonString): String { - return ok("不支持", echo) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/ScanQRCode.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/ScanQRCode.kt deleted file mode 100644 index 54936c93..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/ScanQRCode.kt +++ /dev/null @@ -1,18 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import android.util.Base64 -import com.tencent.mobileqq.qroute.QRoute -import com.tencent.mobileqq.qrscan.api.IQRCodeApi -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("scan_qrcode") -internal object ScanQRCode: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val qrcode = QRoute.api(IQRCodeApi::class.java) - val picBytes = Base64.decode(session.getString("pic"), Base64.DEFAULT) - qrcode.scanImage(picBytes, 0, picBytes.size) - return qrcode.version - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendForwardMessage.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendForwardMessage.kt deleted file mode 100644 index fd7dcd4b..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendForwardMessage.kt +++ /dev/null @@ -1,83 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import kotlinx.serialization.json.* -import moe.fuqiuluo.qqinterface.servlet.MsgSvc -import moe.fuqiuluo.qqinterface.servlet.msg.toJson -import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.helper.ParamsException -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.service.data.SendForwardMessageResult -import moe.fuqiuluo.shamrock.tools.* -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("send_forward_msg", ["send_forward_message"]) -internal object SendForwardMessage : IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val detailType = session.getStringOrNull("detail_type") ?: session.getStringOrNull("message_type") - try { - val chatType = detailType?.let { - MessageHelper.obtainMessageTypeByDetailType(it) - } ?: run { - if (session.has("user_id")) { - if (session.has("group_id")) { - MsgConstant.KCHATTYPETEMPC2CFROMGROUP - } else { - MsgConstant.KCHATTYPEC2C - } - } else if (session.has("group_id")) { - MsgConstant.KCHATTYPEGROUP - } else { - return noParam("detail_type/message_type", session.echo) - } - } - val peerId = when (chatType) { - MsgConstant.KCHATTYPEGROUP -> session.getStringOrNull("group_id") - ?: return noParam("group_id", session.echo) - MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getStringOrNull("user_id") - ?: return noParam("user_id", session.echo) - else -> error("unknown chat type: $chatType") - } - val fromId = session.getStringOrNull("group_id") - val retryCnt = session.getIntOrNull("retry_cnt") ?: 5 - return if (session.isArray("messages")) { - val messages = session.getArray("messages") - invoke(chatType, peerId, fromId ?: peerId, messages, retryCnt, session.echo) - } else { - logic("未知格式合并转发消息", session.echo) - } - } catch (e: ParamsException) { - return noParam(e.message!!, session.echo) - } catch (e: Throwable) { - return logic(e.message ?: e.toString(), session.echo) - } - } - - suspend operator fun invoke( - chatType: Int, - peerId: String, - fromId: String = peerId, - messages: JsonArray, - retryCnt: Int, - echo: JsonElement = EmptyJsonString - ): String { - kotlin.runCatching { - val message = MsgSvc.uploadMultiMsg(chatType, peerId, fromId, messages, retryCnt).onFailure { - return error(it.message ?: it.stackTraceToString(), echo) - }.getOrThrow() - val result = MsgSvc.sendToAio(chatType, peerId, listOf(message).toJson(), fromId, retryCnt).onFailure { - return error(it.message ?: it.stackTraceToString(), echo) - }.getOrThrow() - return ok(SendForwardMessageResult( - msgId = result.msgHashId, - resId = message.data["id"] as String - ), echo = echo) - }.onFailure { - return error("合并转发消息失败: $it", echo) - } - return logic("合并转发消息失败(unknown error)", echo) - } - - override val requiredParams: Array = arrayOf("messages") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendGroupForwardMessage.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendGroupForwardMessage.kt deleted file mode 100644 index c15996ac..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendGroupForwardMessage.kt +++ /dev/null @@ -1,28 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers; - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("send_group_forward_msg", ["send_group_forward_message"]) -internal object SendGroupForwardMessage: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - val retryCnt = session.getIntOrNull("retry_cnt") ?: 5 - return if (session.isArray("messages")) { - val messages = session.getArray("messages") - SendForwardMessage( - MsgConstant.KCHATTYPEGROUP, - groupId.toString(), - messages = messages, - retryCnt = retryCnt, - echo = session.echo - ) - } else { - logic("未知格式合并转发消息", session.echo) - } - } - - override val requiredParams: Array = arrayOf("messages", "group_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendGroupMessage.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendGroupMessage.kt deleted file mode 100644 index eaae6e99..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendGroupMessage.kt +++ /dev/null @@ -1,36 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import moe.fuqiuluo.shamrock.helper.ParamsException -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.jsonArray -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("send_group_message", ["send_group_msg"]) -internal object SendGroupMessage: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - try{ - val groupId = session.getLong("group_id") - val retryCnt = session.getIntOrNull("retry_cnt") - val recallDuration = session.getLongOrNull("recall_duration") - return if (session.isString("message")) { - val autoEscape = session.getBooleanOrDefault("auto_escape", false) - val message = session.getString("message") - SendMessage(MsgConstant.KCHATTYPEGROUP, groupId.toString(), message, autoEscape, echo = session.echo, retryCnt = retryCnt ?: 5, recallDuration = recallDuration) - } else if (session.isObject("message")) { - val message = session.getObject("message") - SendMessage(MsgConstant.KCHATTYPEGROUP, groupId.toString(), listOf( message ).jsonArray, session.echo, retryCnt = retryCnt ?: 5, recallDuration = recallDuration) - } else { - val message = session.getArray("message") - SendMessage(MsgConstant.KCHATTYPEGROUP, groupId.toString(), message, session.echo, retryCnt = retryCnt ?: 5, recallDuration = recallDuration) - } - } catch (e: ParamsException) { - return noParam(e.message!!, session.echo) - } catch (e: Throwable) { - return logic(e.message ?: e.toString(), session.echo) - } - } - - override val requiredParams: Array = arrayOf("message", "group_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendGroupNotice.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendGroupNotice.kt deleted file mode 100644 index 5881eb91..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendGroupNotice.kt +++ /dev/null @@ -1,38 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.qqinterface.servlet.MsgSvc -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("send_group_notice", ["send_group_announcement"]) -internal object SendGroupNotice: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - val text = session.getString("content") - val image = session.getStringOrNull("image") - return invoke(groupId, text, image, session.echo) - } - - suspend operator fun invoke(groupId: Long, text: String, image: String?, echo: JsonElement = EmptyJsonString): String { - val groupAnnouncementMessageImage = if (image != null) { - GroupSvc.uploadImageTroopNotice(image).onFailure { - LogCenter.log("上传群公告图片失败:${it.message}", Level.WARN) - }.getOrNull() - } else null - val announcements = GroupSvc.addQunNotice(groupId, text, groupAnnouncementMessageImage) - if (announcements.isSuccess) { - return ok(announcements.getOrNull(), echo) - } - return logic(announcements.exceptionOrNull()?.message ?: "", echo) - - } - - override val requiredParams: Array = arrayOf("group_id", "content") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendGroupSign.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendGroupSign.kt deleted file mode 100644 index 70d28dc4..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendGroupSign.kt +++ /dev/null @@ -1,35 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.qqinterface.servlet.structures.GuildInfo -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("send_group_sign") -internal object SendGroupSign: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - return invoke(groupId, session.echo) - } - - suspend operator fun invoke(groupId: Long, echo: JsonElement = EmptyJsonString): String { - val ret = GroupSvc.groupSign(groupId) - return if (ret.isSuccess) { - ok(Message(message = ret.getOrNull() ?: ""), echo, "成功") - } else { - logic(ret.exceptionOrNull()?.message ?: "", echo) - } - } - - override val requiredParams: Array = arrayOf("group_id") - - @Serializable - data class Message( - @SerialName("message") var message: String = "" - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendGuildMessage.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendGuildMessage.kt deleted file mode 100644 index 631f33c9..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendGuildMessage.kt +++ /dev/null @@ -1,112 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.jsonArray -import moe.fuqiuluo.qqinterface.servlet.MsgSvc -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.service.data.MessageResult -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.tools.json -import moe.fuqiuluo.shamrock.tools.jsonArray -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("send_guild_message", ["send_guild_msg", "send_guild_channel_msg"]) -internal object SendGuildMessage: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val guildId = session.getString("guild_id").toULong() - val channelId = session.getString("channel_id").toULong() - val retryCnt = session.getIntOrNull("retry_cnt") ?: 3 - val recallDuration = session.getLongOrNull("recall_duration") - return if (session.isString("message")) { - val autoEscape = session.getBooleanOrDefault("auto_escape", false) - val message = session.getString("message") - return invoke(guildId, channelId, message, autoEscape, retryCnt, recallDuration, echo = session.echo) - } else if (session.isArray("message")) { - val message = session.getArray("message") - return invoke(guildId, channelId, message, echo = session.echo, retryCnt = retryCnt, recallDuration = recallDuration) - } else { - val message = session.getObject("message") - invoke(guildId, channelId, listOf(message).jsonArray, session.echo, retryCnt, recallDuration = recallDuration) - } - } - - suspend operator fun invoke( - guildId: ULong, - channelId: ULong, - message: String, - autoEscape: Boolean, - retryCnt: Int, - recallDuration: Long?, - echo: JsonElement = EmptyJsonString - ): String { - val result = if (autoEscape) { - MsgSvc.sendToAio(MsgConstant.KCHATTYPEGUILD, guildId.toString(), listOf( - mapOf( - "type" to "text", - "data" to mapOf( - "text" to message - ) - ) - ).json, fromId = channelId.toString(), retryCnt) - } else { - val msg = MessageHelper.decodeCQCode(message) - if (msg.isEmpty()) { - LogCenter.log("CQ码不合法", Level.WARN) - return logic("CQCode is illegal", echo) - } else { - MsgSvc.sendToAio(MsgConstant.KCHATTYPEGUILD, guildId.toString(), msg, fromId = channelId.toString(), retryCnt) - } - } - if (result.isFailure) { - return logic(result.exceptionOrNull()?.message ?: "", echo) - } - val sendMsgResult = result.getOrThrow() - if (sendMsgResult.msgHashId <= 0) { - return logic("send message failed", echo = echo) - } - recallDuration?.let { autoRecall(sendMsgResult.msgHashId, it) } - return ok( - MessageResult( - msgId = sendMsgResult.msgHashId, - time = (sendMsgResult.msgTime * 0.001).toLong() - ), echo = echo) - } - - suspend operator fun invoke( - guildId: ULong, channelId: ULong, message: JsonArray, echo: JsonElement = EmptyJsonString, retryCnt: Int, recallDuration: Long?, - ): String { - val result = MsgSvc.sendToAio(MsgConstant.KCHATTYPEGUILD, guildId.toString(), message, fromId = channelId.toString(), retryCnt) - if (result.isFailure) { - return logic(result.exceptionOrNull()?.message ?: "", echo) - } - val sendMsgResult = result.getOrThrow() - if (sendMsgResult.msgHashId <= 0) { - return logic("send message failed", echo = echo) - } - recallDuration?.let { autoRecall(sendMsgResult.msgHashId, it) } - return ok(MessageResult( - msgId = sendMsgResult.msgHashId, - time = (sendMsgResult.msgTime * 0.001).toLong() - ), echo) - } - - override val requiredParams: Array = arrayOf("guild_id", "channel_id", "message") - - private fun autoRecall(msgHash: Int, duration: Long) { - GlobalScope.launch(Dispatchers.Default) { - delay(duration) - MsgSvc.recallMsg(msgHash) - } - } - -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendLike.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendLike.kt deleted file mode 100644 index b9f413b8..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendLike.kt +++ /dev/null @@ -1,29 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.VisitorSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.tools.errMsg -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("send_like") -internal object SendLike: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val times = session.getInt("times") - val uin = session.getLong("user_id") - return invoke(uin, times, session.echo) - } - - suspend operator fun invoke(uin: Long, cnt: Int, echo: JsonElement = EmptyJsonString): String { - val result = VisitorSvc.vote(uin, cnt) - return if(result.isSuccess) { - ok("成功", echo) - } else { - logic(result.errMsg(), echo) - } - } - - override val requiredParams: Array = arrayOf("times", "user_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendMessage.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendMessage.kt deleted file mode 100644 index 6c9ae7cd..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendMessage.kt +++ /dev/null @@ -1,187 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.helper.ParamsException -import moe.fuqiuluo.qqinterface.servlet.MsgSvc -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.shamrock.remote.service.data.MessageResult -import moe.fuqiuluo.shamrock.tools.json -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.tools.jsonArray -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("send_msg", ["send_message"]) -internal object SendMessage : IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val detailType = session.getStringOrNull("detail_type") ?: session.getStringOrNull("message_type") - try { - val chatType = detailType?.let { - MessageHelper.obtainMessageTypeByDetailType(it) - } ?: run { - if (session.has("user_id")) { - if (session.has("group_id")) { - MsgConstant.KCHATTYPETEMPC2CFROMGROUP - } else { - MsgConstant.KCHATTYPEC2C - } - } else if (session.has("group_id")) { - MsgConstant.KCHATTYPEGROUP - } else { - return noParam("detail_type/message_type", session.echo) - } - } - val peerId = when (chatType) { - MsgConstant.KCHATTYPEGROUP -> session.getLongOrNull("group_id") ?: return noParam( - "group_id", - session.echo - ) - - MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getLongOrNull("user_id") - ?: return noParam("user_id", session.echo) - - else -> error("unknown chat type: $chatType") - }.toString() - val fromId = when (chatType) { - MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getLongOrNull("group_id") - ?: return noParam("group_id", session.echo) - - MsgConstant.KCHATTYPEC2C -> session.getLongOrNull("user_id") ?: return noParam("user_id", session.echo) - else -> error("unknown chat type: $chatType") - }.toString() - val retryCnt = session.getIntOrNull("retry_cnt") ?: 5 - val recallDuration = session.getLongOrNull("recall_duration") - return if (session.isString("message")) { - val autoEscape = session.getBooleanOrDefault("auto_escape", false) - val message = session.getString("message") - invoke( - chatType, - peerId, - message, - autoEscape, - echo = session.echo, - fromId = fromId, - retryCnt = retryCnt, - recallDuration = recallDuration - ) - } else if (session.isArray("message")) { - val message = session.getArray("message") - invoke( - chatType, - peerId, - message, - session.echo, - fromId = fromId, - retryCnt = retryCnt, - recallDuration = recallDuration - ) - } else { - val message = session.getObject("message") - invoke( - chatType, - peerId, - listOf(message).jsonArray, - session.echo, - fromId = fromId, - retryCnt = retryCnt, - recallDuration = recallDuration - ) - } - } catch (e: ParamsException) { - return noParam(e.message!!, session.echo) - } catch (e: Throwable) { - return logic(e.message ?: e.toString(), session.echo) - } - } - - // 发送文本格式/CQ码类型消息 - suspend operator fun invoke( - chatType: Int, - peerId: String, - message: String, - autoEscape: Boolean, - fromId: String = peerId, - retryCnt: Int, - recallDuration: Long?, - echo: JsonElement = EmptyJsonString - ): String { - val result = if (autoEscape) { - MsgSvc.sendToAio( - chatType, peerId, listOf( - mapOf( - "type" to "text", - "data" to mapOf( - "text" to message - ) - ) - ).json, fromId = fromId, retryCnt - ) - } else { - val msg = MessageHelper.decodeCQCode(message) - if (msg.isEmpty()) { - LogCenter.log("CQ码不合法", Level.WARN) - return logic("CQCode is illegal", echo) - } else { - MsgSvc.sendToAio(chatType, peerId, msg, fromId = fromId, retryCnt) - } - }.getOrElse{ return logic(it.message ?: "", echo)} - if (result.msgHashId <= 0) { - return logic("send message failed", echo = echo) - } - if (recallDuration != null) { - GlobalScope.launch(Dispatchers.Default) { - delay(recallDuration) - MsgSvc.recallMsg(result.msgHashId) - } - } - return ok( - MessageResult( - msgId = result.msgHashId, - time = (result.msgTime * 0.001).toLong() - ), echo = echo - ) - } - - // 消息段格式消息 - suspend operator fun invoke( - chatType: Int, - peerId: String, - message: JsonArray, - echo: JsonElement = EmptyJsonString, - fromId: String = peerId, - retryCnt: Int, - recallDuration: Long?, - ): String { - //if (!ContactHelper.checkContactAvailable(chatType, peerId)) { - // return logic("contact is not found", echo = echo) - //} - val result = MsgSvc.sendToAio(chatType, peerId, message, fromId, retryCnt) - .getOrElse { return logic(it.message ?: "", echo) } - if (result.msgHashId <= 0) { - return logic("send message failed", echo = echo) - } - if (recallDuration != null) { - GlobalScope.launch(Dispatchers.Default) { - delay(recallDuration) - MsgSvc.recallMsg(result.msgHashId) - } - } - return ok( - MessageResult( - msgId = result.msgHashId, - time = (result.msgTime * 0.001).toLong() - ), echo - ) - } - - override val requiredParams: Array = arrayOf("message") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendMsgByResid.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendMsgByResid.kt deleted file mode 100644 index 1723a161..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendMsgByResid.kt +++ /dev/null @@ -1,59 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.atomicfu.atomic -import kotlinx.serialization.json.JsonElement - -import moe.fuqiuluo.qqinterface.servlet.BaseSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler -import protobuf.auto.toByteArray -import protobuf.message.* -import protobuf.message.element.GeneralFlags -import protobuf.message.routing.C2C -import protobuf.message.routing.Grp -import kotlin.random.Random -import kotlin.random.nextUInt - -@OneBotHandler("send_msg_by_resid", ["send_message_by_resid"]) -internal object SendMsgByResid : IActionHandler() { - private val msgSeq = atomic(1000) - - override suspend fun internalHandle(session: ActionSession): String { - val resId = session.getString("res_id") - val peerId = session.getString("peer_id") - val messageType = session.getString("message_type") - return invoke(peerId, resId, messageType, session.echo) - } - - suspend operator fun invoke(peerId: String, resId: String, messageType: String, echo: JsonElement = EmptyJsonString): String { - val req = PbSendMsgReq( - routingHead = when (messageType) { - "group" -> RoutingHead(grp = Grp(peerId.toUInt())) - "private" -> RoutingHead(c2c = C2C(peerId.toUInt())) - else -> RoutingHead(grp = Grp(peerId.toUInt())) - }, - contentHead = ContentHead(1, 0, 0, 0), - msgBody = MsgBody( - richText = RichText( - elements = arrayListOf( - Elem( - generalFlags = GeneralFlags( - longTextFlag = 1u, - longTextResid = resId - ) - ) - ) - ) - ), - msgSeq = msgSeq.incrementAndGet().toUInt(), - msgRand = Random.nextUInt(), - msgVia = 0u - ) - BaseSvc.sendBufferAW("MessageSvc.PbSendMsg", true, req.toByteArray()) - return ok("ok", echo) - } - - override val requiredParams: Array = arrayOf("res_id", "peer_id", "message_type") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendPrivateForwardMessage.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendPrivateForwardMessage.kt deleted file mode 100644 index fade95bc..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendPrivateForwardMessage.kt +++ /dev/null @@ -1,30 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers; - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("send_private_forward_msg", ["send_private_forward_message"]) -internal object SendPrivateForwardMessage : IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val userId = session.getLong("user_id") - val groupId = session.getLongOrNull("group_id") - val retryCnt = session.getIntOrNull("retry_cnt") ?: 5 - return if (session.isArray("messages")) { - val messages = session.getArray("messages") - SendForwardMessage( - MsgConstant.KCHATTYPEC2C, - userId.toString(), - groupId?.toString() ?: userId.toString(), - messages, - retryCnt, - echo = session.echo - ) - } else { - logic("未知格式合并转发消息", session.echo) - } - } - - override val requiredParams: Array = arrayOf("messages", "user_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendPrivateMessage.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendPrivateMessage.kt deleted file mode 100644 index fcd355b0..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendPrivateMessage.kt +++ /dev/null @@ -1,47 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import moe.fuqiuluo.qqinterface.servlet.TicketSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.jsonArray -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("send_private_msg", ["send_private_message", "send_friend_msg"]) -internal object SendPrivateMessage : IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val userId = session.getString("user_id").let { - if (it == "self") TicketSvc.getUin() else it - } - val groupId = session.getLongOrNull("group_id") - val chatType = if (groupId == null) MsgConstant.KCHATTYPEC2C else MsgConstant.KCHATTYPETEMPC2CFROMGROUP - val retryCnt = session.getIntOrNull("retry_cnt") - val recallDuration = session.getLongOrNull("recall_duration") - return if (session.isString("message")) { - val autoEscape = session.getBooleanOrDefault("auto_escape", false) - val message = session.getString("message") - SendMessage( - chatType = chatType, - peerId = userId, - message = message, - autoEscape = autoEscape, - echo = session.echo, - fromId = groupId?.toString() ?: userId, - retryCnt = retryCnt ?: 5, - recallDuration = recallDuration - ) - } else { - SendMessage( - chatType = chatType, - peerId = userId, - message = if (session.isArray("message")) session.getArray("message") else listOf(session.getObject("message")).jsonArray, - echo = session.echo, - fromId = groupId?.toString() ?: userId, - retryCnt = retryCnt ?: 5, - recallDuration = recallDuration - ) - } - } - - override val requiredParams: Array = arrayOf("message", "user_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetEssenceMessage.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetEssenceMessage.kt deleted file mode 100644 index c1a30c55..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetEssenceMessage.kt +++ /dev/null @@ -1,34 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.qqinterface.servlet.MsgSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("set_essence_msg", ["set_essence_message"]) -internal object SetEssenceMessage: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val messageId = session.getInt("message_id") - return invoke(messageId, session.echo) - } - - suspend operator fun invoke(messageId: Int, echo: JsonElement = EmptyJsonString): String { - val msg = MsgSvc.getMsg(messageId).onFailure { - return logic("Obtain msg failed, please check your msg_id.", echo) - }.getOrThrow() - val (success, tip) = GroupSvc.setEssenceMessage( - groupId = if (msg.chatType == MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0, - seq = msg.msgSeq, - rand = msg.msgRandom - ) - return if (success) { - ok("成功", echo) - } else { - logic(tip, echo) - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetFriendAddRequest.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetFriendAddRequest.kt deleted file mode 100644 index 6e8b6a2c..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetFriendAddRequest.kt +++ /dev/null @@ -1,46 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.FriendSvc -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("set_friend_add_request") -internal object SetFriendAddRequest: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val flag = session.getString("flag") - val approve = session.getBooleanOrDefault("approve", true) - val remark = session.getStringOrNull("remark") - val notSeen = session.getBooleanOrDefault("notSeen", false) - return invoke(flag, approve, remark, notSeen, session.echo) - } - - suspend operator fun invoke(flag: String, approve: Boolean? = true, remark: String? = "", notSeen: Boolean? = false, echo: JsonElement = EmptyJsonString): String { - val flags = flag.split(";") - var ts = flags[0].toLong() -// val src = flags[1].toInt() -// val subSrc = flags[2].toInt() - val applier = flags[3].toLong() - if (ts.toString().length < 13) { - // time but not seq, query seq again - val reqs = FriendSvc.requestFriendSystemMsgNew(20, 0, 0, 1) - val req = reqs?.firstOrNull { - it.msg_time.get() == ts - } - // 好友请求seq貌似就是time*1000,查不到直接*1000 - ts = req?.msg_seq?.get() ?: (ts * 1000) - } - return try { - FriendSvc.requestFriendRequest(ts, applier, remark ?: "", approve, notSeen) - ok("成功", echo) - } catch (err: Throwable) { - err.printStackTrace() - error("失败:${err.message}", echo) - } - } - - override val requiredParams: Array = arrayOf("flag") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetGroupAddRequest.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetGroupAddRequest.kt deleted file mode 100644 index 15535266..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetGroupAddRequest.kt +++ /dev/null @@ -1,57 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("set_group_add_request") -internal object SetGroupAddRequest: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val flag = session.getString("flag") - val approve = session.getBooleanOrDefault("approve", true) - val remark = session.getStringOrNull("reason") - val notSeen = session.getBooleanOrDefault("not_seen", false) - val subType = session.getString("sub_type") - return invoke(flag, approve, subType, remark, notSeen, session.echo) - } - - suspend operator fun invoke(flag: String, approve: Boolean? = true, subType: String, remark: String? = "", notSeen: Boolean? = false, echo: JsonElement = EmptyJsonString): String { - val flags = flag.split(";") - var ts = flags[0].toLong() - try { - if (ts.toString().length < 13) { - // time but not seq, query seq again - var reqs = GroupSvc.requestGroupSystemMsgNew(20, 1) - val riskReqs = GroupSvc.requestGroupSystemMsgNew(20, 2) - reqs = reqs + riskReqs - val req = reqs.firstOrNull { - it.msg_time.get() == ts - } - ts = req?.msg_seq?.get() ?: return error("失败:未找到该请求", echo) - } - } catch (err: Throwable) { - LogCenter.log(err.stackTraceToString(), Level.WARN) - return error("查找请求失败:${err.message}", echo) - } - val groupCode = flags[1].toLong() - val uin = flags[2].toLong() - return try { - val result = GroupSvc.requestGroupRequest(ts, uin, groupCode, remark ?: "", approve, notSeen, subType) - if (result.isSuccess) { - ok(result.getOrNull(), echo) - } else { - logic(result.exceptionOrNull()?.message ?: "", echo) - } - } catch (err: Throwable) { - err.printStackTrace() - error("失败:${err.message}", echo) - } - } - - override val requiredParams: Array = arrayOf("flag", "sub_type") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetGroupAdmin.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetGroupAdmin.kt deleted file mode 100644 index d6f205d8..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetGroupAdmin.kt +++ /dev/null @@ -1,26 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("set_group_admin") -internal object SetGroupAdmin: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - val userId = session.getLong("user_id") - val enable = session.getBoolean("enable") - return invoke(groupId, userId, enable, session.echo) - } - - operator fun invoke(groupId: Long, userId: Long, enable: Boolean, echo: JsonElement = EmptyJsonString): String { - if (!GroupSvc.isOwner(groupId)) { - return logic("you are not owner", echo) - } - GroupSvc.setGroupAdmin(groupId, userId, enable) - return ok("成功", echo) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetGroupCommentFace.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetGroupCommentFace.kt deleted file mode 100644 index da9b32cb..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetGroupCommentFace.kt +++ /dev/null @@ -1,30 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.ChatSvc -import moe.fuqiuluo.qqinterface.servlet.MsgSvc -import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("set_group_comment_face") -internal object SetGroupCommentFace: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - val msgId = session.getIntOrNull("msg_id") ?: session.getInt("message_id") - val faceId = session.getInt("face_id") - val isSet = session.getBooleanOrDefault("is_set", true) - return invoke(groupId, msgId, faceId, isSet, session.echo) - } - - operator fun invoke(groupId: Long, msgHash: Int, faceIndex: Int, isSet: Boolean, echo: JsonElement = EmptyJsonString): String { - val mapping = MessageHelper.getMsgMappingByHash(msgHash) - ?: return error("failed to locate message", echo = echo) - ChatSvc.setGroupMessageCommentFace(groupId, mapping.msgSeq.toULong(), faceIndex.toString(), isSet) - return ok("success", echo = echo) - } - - override val requiredParams: Array = arrayOf("group_id", "face_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetGroupUnique.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetGroupUnique.kt deleted file mode 100644 index b533eb26..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetGroupUnique.kt +++ /dev/null @@ -1,28 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("set_group_special_title") -internal object SetGroupUnique: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - val userId = session.getLong("user_id") - val unique = session.getString("special_title") - return invoke(groupId, userId, unique, session.echo) - } - - suspend operator fun invoke(groupId: Long, userId: Long, unique: String, echo: JsonElement = EmptyJsonString): String { - if (!GroupSvc.isOwner(groupId)) { - return error("you are not owner", echo) - } - GroupSvc.setGroupUniqueTitle(groupId, userId, unique) - return ok("成功", echo) - } - - override val requiredParams: Array = arrayOf("group_id", "user_id", "special_title") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetGroupWholeBan.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetGroupWholeBan.kt deleted file mode 100644 index ff187642..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetGroupWholeBan.kt +++ /dev/null @@ -1,24 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("set_group_whole_ban") -internal object SetGroupWholeBan: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - val enable = session.getBoolean("enable") - return invoke(groupId, enable, session.echo) - } - - operator fun invoke(groupId: Long, enable: Boolean, echo: JsonElement = EmptyJsonString): String { - GroupSvc.setGroupWholeBan(groupId, enable) - return ok("成功", echo) - } - - override val requiredParams: Array = arrayOf() -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetGuildMemberRole.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetGuildMemberRole.kt deleted file mode 100644 index 178c5f9f..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetGuildMemberRole.kt +++ /dev/null @@ -1,42 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GProSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.tools.asString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("set_guild_member_role") -internal object SetGuildMemberRole: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val guildId = session.getString("guild_id").toULong() - val role = session.getString("role_id").toULong() - val set = session.getBooleanOrDefault("set", false) - return if (session.has("user_id")) { - val userId = session.getLong("user_id").toULong() - invoke(guildId, userId, role, set, echo = session.echo) - } else if (session.isArray("users")) { - invoke(guildId, session.getArray("users").map { - it.asString.toULong() - }, role, set, echo = session.echo) - } else { - logic("missing user_id or users", echo = session.echo) - } - } - - operator fun invoke(guildId: ULong, users: List, roleId: ULong, set: Boolean, echo: JsonElement = EmptyJsonString): String { - users.forEach { - GProSvc.setMemberRole(guildId, it, roleId, set) - } - return ok("success", echo = echo) - } - - operator fun invoke(guildId: ULong, user: ULong, roleId: ULong, set: Boolean, echo: JsonElement = EmptyJsonString): String { - GProSvc.setMemberRole(guildId, user, roleId, set) - return ok("success", echo = echo) - } - - override val requiredParams: Array = arrayOf("guild_id", "role_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetModelShow.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetModelShow.kt deleted file mode 100644 index beded260..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetModelShow.kt +++ /dev/null @@ -1,35 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.qqinterface.servlet.CardSvc -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("_set_model_show") -internal object SetModelShow : IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val model = session.getString("model") - val manu = session.getStringOrNull("manu") ?: session.getString("model_show") - val modelShow = session.getStringOrNull("modelshow") ?: "Android" - val imei = session.getStringOrNull("imei") ?: PlatformUtils.getAndroidID() - val show = session.getBooleanOrDefault("show", true) - return invoke(model, manu, modelShow, imei, show, session.echo) - } - - suspend operator fun invoke( - model: String, - manu: String, - modelShow: String, - imei: String, - show: Boolean, - echo: JsonElement = EmptyJsonString - ): String { - CardSvc.setModelShow(model, manu, modelShow, imei, show) - return ok("成功", echo = echo) - } - - override val requiredParams: Array = arrayOf("model") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetProfileCard.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetProfileCard.kt deleted file mode 100644 index 162a1be2..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SetProfileCard.kt +++ /dev/null @@ -1,45 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import android.os.Bundle -import moe.fuqiuluo.shamrock.remote.service.data.profile.ProfileProtocolConst -import com.tencent.mobileqq.profilecard.api.IProfileProtocolService -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher -import moe.fuqiuluo.symbols.OneBotHandler -import mqq.app.MobileQQ - -@OneBotHandler("set_qq_profile") -internal object SetProfileCard: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val nickName = session.getString("nickname") - val company = session.getString("company") - val email = session.getString("email") - val college = session.getString("college") - val personalNote = session.getString("personal_note") - - val birthday = session.getIntOrNull("birthday") - val age = session.getIntOrNull("age") - - val bundle = Bundle() - val service = AppRuntimeFetcher.appRuntime - .getRuntimeService(IProfileProtocolService::class.java, "all") - bundle.putString(ProfileProtocolConst.KEY_NICK, nickName) - bundle.putString(ProfileProtocolConst.KEY_COMPANY, company) - bundle.putString(ProfileProtocolConst.KEY_EMAIL, email) - bundle.putString(ProfileProtocolConst.KEY_COLLEGE, college) - bundle.putString(ProfileProtocolConst.KEY_PERSONAL_NOTE, personalNote) - - if (birthday != null) { - bundle.putInt(ProfileProtocolConst.KEY_BIRTHDAY, birthday.toInt()) - } - if (age != null) { - bundle.putInt(ProfileProtocolConst.KEY_AGE, age.toInt()) - } - - service.setProfileDetail(bundle) - return ok("设置成功", session.echo) - } - - override val requiredParams: Array = arrayOf("nickname", "company", "email", "college", "personal_note") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SwitchAccount.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SwitchAccount.kt deleted file mode 100644 index 2a98cbc0..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SwitchAccount.kt +++ /dev/null @@ -1,35 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher -import moe.fuqiuluo.symbols.OneBotHandler -import mqq.app.MobileQQ - -@OneBotHandler("switch_account") -internal object SwitchAccount: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val userId = session.getLong("user_id") - return invoke(userId, session.echo) - } - - operator fun invoke( - userId: Long, - echo: JsonElement = EmptyJsonString - ): String { - val account = MobileQQ.getMobileQQ().allAccounts.firstOrNull { it.uin == userId.toString() } - ?: return error("账号不存在", echo) - val runtime = AppRuntimeFetcher.appRuntime - val result = kotlin.runCatching { - runtime.switchAccount(account, null) - } - if (result.isFailure) { - return error(result.exceptionOrNull()?.message ?: "切换账号失败", echo) - } - return ok("切换成功", echo) - } - - override val requiredParams: Array = arrayOf("user_id") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/TestHandler.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/TestHandler.kt deleted file mode 100644 index 50b2a6b8..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/TestHandler.kt +++ /dev/null @@ -1,31 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.Serializable -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import de.robv.android.xposed.XposedBridge.log -import moe.fuqiuluo.shamrock.remote.structures.Status -import moe.fuqiuluo.shamrock.remote.structures.resultToString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("test") -internal object TestHandler: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - kotlin.runCatching { - val msg = StringBuffer() - return resultToString( - isOk = true, - code = Status.Ok, - data = Test(System.currentTimeMillis()), - msg = msg.toString(), - echo = session.echo - ) - }.onFailure { - log(it) - } - return "error" - } - - @Serializable - data class Test(val time: Long) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UpdateGuildRole.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UpdateGuildRole.kt deleted file mode 100644 index 85940d30..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UpdateGuildRole.kt +++ /dev/null @@ -1,28 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.GProSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("update_guild_role") -internal object UpdateGuildRole: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val guildId = session.getString("guild_id").toULong() - val roleId = session.getString("role_id").toULong() - val name = session.getString("name") - val color = session.getLong("color") - return invoke(guildId, roleId, name, color, session.echo) - } - - suspend operator fun invoke(guildId: ULong, roleId: ULong, name: String, color: Long, echo: JsonElement = EmptyJsonString): String { - val result = GProSvc.updateGuildRole(guildId, roleId, name, color).onFailure { - return error(it.message ?: "Unknown error", echo) - } - return ok("success", echo = echo) - } - - override val requiredParams: Array = arrayOf("role_id", "guild_id", "name", "color") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadFileToShamrock.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadFileToShamrock.kt deleted file mode 100644 index a89b0736..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadFileToShamrock.kt +++ /dev/null @@ -1,59 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import android.util.Base64 -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.tools.hex2ByteArray -import moe.fuqiuluo.shamrock.tools.toHexString -import moe.fuqiuluo.shamrock.utils.FileUtils -import moe.fuqiuluo.symbols.OneBotHandler -import java.io.RandomAccessFile - -@OneBotHandler("upload_file_to_shamrock") -internal object UploadFileToShamrock: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val md5 = session.getString("md5").hex2ByteArray() - val offset = session.getStringOrNull("offset")?.toULong() ?: 0uL - val chunk = Base64.decode(session.getString("chunk"), Base64.DEFAULT) - val fileSize = session.getStringOrNull("file_size")?.toULong() ?: chunk.size.toULong() - return invoke(md5, fileSize, offset, chunk, session.echo) - } - - operator fun invoke( - md5: ByteArray, - fileSize: ULong, - offset: ULong, - chunk: ByteArray, - echo: JsonElement = EmptyJsonString - ): String { - val file = FileUtils.getFileByMd5(md5.toHexString()) - runCatching { - if (!file.exists()) { - file.createNewFile() - } - val rd = RandomAccessFile(file, "rw") - rd.setLength(fileSize.toLong()) - rd.seek(offset.toLong()) - rd.write(chunk, 0, chunk.size) - rd.close() - }.onFailure { - return error(it.message ?: it.toString(), echo) - } - return ok(UploadFileResult( - fileSize = fileSize, - isFinish = fileSize <= offset + chunk.size.toULong(), - filePath = file.absolutePath - ), echo = echo) - } - - @Serializable - data class UploadFileResult( - @SerialName("file_size") val fileSize: ULong, - @SerialName("finish") val isFinish: Boolean, - @SerialName("path") val filePath: String - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadGroupFile.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadGroupFile.kt deleted file mode 100644 index 5ee6010f..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadGroupFile.kt +++ /dev/null @@ -1,163 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.media.MediaMetadataRetriever -import com.tencent.mobileqq.qroute.QRoute -import com.tencent.qqnt.kernel.nativeinterface.FileElement -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.msg.api.IMsgService -import com.tencent.qqnt.msg.api.IMsgUtilApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.withContext -import kotlinx.coroutines.withTimeoutOrNull -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.transfile.RichMediaUploadHandler -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.helper.TransfileHelper -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.utils.FileUtils -import moe.fuqiuluo.shamrock.utils.MD5 -import moe.fuqiuluo.symbols.OneBotHandler -import java.io.File -import java.io.FileOutputStream -import kotlin.coroutines.resume - -@OneBotHandler("upload_group_file") -internal object UploadGroupFile : IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getLong("group_id") - val file = session.getString("file") - val name = session.getString("name") - .replace("/", "_") - .replace("\\", "_") - .replace("\n", "_") - .replace("\t", "_") - .replace("\r", "_") - return invoke(groupId, file, name, session.echo) - } - - suspend operator fun invoke( - groupId: Long, - file: String, - name: String, - echo: JsonElement = EmptyJsonString - ): String { - var srcFile = File(file) - if (!srcFile.exists()) { - srcFile = FileUtils.getFile(file) - } - if (!srcFile.exists()) { - srcFile = file.let { - val md5 = it.replace( - regex = "[{}\\-]".toRegex(), - replacement = "" - ).split(".")[0].lowercase() - if (md5.length == 32) { - FileUtils.getFileByMd5(it) - } else { - FileUtils.parseAndSave(it) - } - } - } - - if (!srcFile.exists()) { - return badParam("文件不存在", echo) - } - - val fileElement = FileElement() - fileElement.fileMd5 = "" - fileElement.fileName = name - fileElement.filePath = srcFile.absolutePath - fileElement.fileSize = srcFile.length() - fileElement.folderId = srcFile.parent ?: "" - fileElement.picWidth = 0 - fileElement.picHeight = 0 - fileElement.videoDuration = 0 - fileElement.picThumbPath = HashMap() - fileElement.expireTime = 0L - fileElement.fileSha = "" - fileElement.fileSha3 = "" - fileElement.file10MMd5 = "" - when (TransfileHelper.getExtensionId(name)) { - 0 -> { - val wh = QRoute.api(IMsgUtilApi::class.java) - .getPicSizeByPath(srcFile.absolutePath) - fileElement.picWidth = wh.first - fileElement.picHeight = wh.second - fileElement.picThumbPath[750] = srcFile.absolutePath - } - 2 -> { - val thumbPic = FileUtils.getFileByMd5(MD5.genFileMd5Hex(srcFile.absolutePath)) - withContext(Dispatchers.IO) { - val fileOutputStream = FileOutputStream(thumbPic) - val retriever = MediaMetadataRetriever() - retriever.setDataSource(fileElement.filePath) - retriever.frameAtTime?.compress(Bitmap.CompressFormat.JPEG, 60, fileOutputStream) - fileOutputStream.flush() - fileOutputStream.close() - } - val options = BitmapFactory.Options() - BitmapFactory.decodeFile(thumbPic.absolutePath, options) - fileElement.picHeight = options.outHeight - fileElement.picWidth = options.outWidth - fileElement.picThumbPath = hashMapOf(750 to thumbPic.absolutePath) - } - } - val msgElement = MsgElement() - msgElement.elementType = MsgConstant.KELEMTYPEFILE - msgElement.fileElement = fileElement - - // 根据文件大小调整超时时间 - val msgIdPair = MessageHelper.generateMsgId(MsgConstant.KCHATTYPEGROUP) - val info = (withTimeoutOrNull((srcFile.length() / (125 * 1024)) * 1000 + 5000) { - val msgService = QRoute.api(IMsgService::class.java) - val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, groupId.toString()) - suspendCancellableCoroutine { - msgService.sendMsgWithMsgId( - contact, msgIdPair.qqMsgId, arrayListOf(msgElement) - ) { code, reason -> - LogCenter.log("群文件消息发送异常(code = $code, reason = $reason)") - it.resume(null) - } - RichMediaUploadHandler.registerListener(msgIdPair.qqMsgId) { - it.resume(this) - return@registerListener true - } - } - } ?: return error("上传文件失败", echo)).also { - if (it.commonFileInfo == null) { - return error(it.fileErrMsg ?: "上传文件失败", echo) - } - }.commonFileInfo - - return ok(data = FileUploadResult( - msgHash = msgIdPair.msgHashId, - bizid = info.bizType ?: 0, - md5 = info.md5, - sha = info.sha, - sha3 = info.sha3, - fileId = info.uuid - ), echo = echo) - } - - override val requiredParams: Array = arrayOf("group_id", "file", "name") - - @Serializable - data class FileUploadResult( - @SerialName("msg_id") val msgHash: Int, - @SerialName("bizid") val bizid: Int, - @SerialName("md5") val md5: String, - @SerialName("sha") val sha: String, - @SerialName("sha3") val sha3: String, - @SerialName("file_id") val fileId: String - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadMultiMessage.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadMultiMessage.kt deleted file mode 100644 index 2a12dc45..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadMultiMessage.kt +++ /dev/null @@ -1,82 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import kotlinx.serialization.json.* -import moe.fuqiuluo.qqinterface.servlet.MsgSvc -import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.helper.ParamsException -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.service.data.UploadForwardMessageResult -import moe.fuqiuluo.shamrock.tools.* -import moe.fuqiuluo.symbols.OneBotHandler - -@OneBotHandler("upload_multi_message") -internal object UploadMultiMessage : IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val detailType = session.getStringOrNull("detail_type") ?: session.getStringOrNull("message_type") - try { - val chatType = detailType?.let { - MessageHelper.obtainMessageTypeByDetailType(it) - } ?: run { - if (session.has("user_id")) { - if (session.has("group_id")) { - MsgConstant.KCHATTYPETEMPC2CFROMGROUP - } else { - MsgConstant.KCHATTYPEC2C - } - } else if (session.has("group_id")) { - MsgConstant.KCHATTYPEGROUP - } else { - return noParam("detail_type/message_type", session.echo) - } - } - val peerId = when (chatType) { - MsgConstant.KCHATTYPEGROUP -> session.getStringOrNull("group_id") - ?: return noParam("group_id", session.echo) - MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getStringOrNull("user_id") - ?: return noParam("user_id", session.echo) - else -> error("unknown chat type: $chatType") - } - val fromId = session.getStringOrNull("group_id") - val retryCnt = session.getIntOrNull("retry_cnt") ?: 5 - return if (session.isArray("messages")) { - val messages = session.getArray("messages") - invoke(chatType, peerId, fromId ?: peerId, messages, retryCnt, echo = session.echo) - } else { - logic("未知格式合并转发消息", session.echo) - } - } catch (e: ParamsException) { - return noParam(e.message!!, session.echo) - } catch (e: Throwable) { - return logic(e.message ?: e.toString(), session.echo) - } - } - - suspend operator fun invoke( - chatType: Int, - peerId: String, - fromId: String = peerId, - messages: JsonArray, - retryCnt: Int, - echo: JsonElement = EmptyJsonString - ): String { - kotlin.runCatching { - MsgSvc.uploadMultiMsg(chatType, peerId, fromId, messages, retryCnt).getOrThrow() - }.onFailure { - return error("合并转发消息失败: ${it.stackTraceToString()}", echo) - }.onSuccess { message -> - return ok( - UploadForwardMessageResult( - resId = message.data["id"] as String, - filename = message.data["filename"] as String, - summary = message.data["summary"] as String, - desc = message.data["desc"] as String - ), echo = echo - ) - } - return logic("合并转发消息失败(unknown error)", echo) - } - - override val requiredParams: Array = arrayOf("messages") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadNtResource.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadNtResource.kt deleted file mode 100644 index 88febbb9..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadNtResource.kt +++ /dev/null @@ -1,82 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.structures.CommFileInfo -import moe.fuqiuluo.qqinterface.servlet.structures.UploadResult -import moe.fuqiuluo.qqinterface.servlet.transfile.NtV2RichMediaSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.utils.FileUtils -import moe.fuqiuluo.symbols.OneBotHandler -import kotlin.time.Duration.Companion.seconds - -@OneBotHandler("upload_nt_resource", ["upload_nt_res"]) -internal object UploadNtResource: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val pic = session.getString("file") - val chatType = when(session.getStringOrNull("message_type")) { - "group" -> MsgConstant.KCHATTYPEGROUP - "guild" -> MsgConstant.KCHATTYPEGUILD - "private" -> MsgConstant.KCHATTYPEC2C - else -> MsgConstant.KCHATTYPEGROUP - } - val fileType = when(session.getStringOrNull("file_type")) { - "file" -> MsgConstant.KELEMTYPEFILE - "image", "pic" -> MsgConstant.KELEMTYPEPIC - "video" -> MsgConstant.KELEMTYPEVIDEO - "audio", "voice", "record" -> MsgConstant.KELEMTYPEPTT - else -> MsgConstant.KELEMTYPEFILE - } - return invoke(chatType, fileType, pic, session.echo) - } - - suspend operator fun invoke( - chatType: Int, - fileType: Int, - picture: String, - echo: JsonElement = EmptyJsonString - ): String { - if (ShamrockConfig.isDev()) { - val file = picture.let { - val md5 = it.replace( - regex = "[{}\\-]".toRegex(), - replacement = "" - ).split(".")[0].lowercase() - if (md5.length == 32) { - FileUtils.getFileByMd5(it) - } else { - FileUtils.parseAndSave(it) - } - } - if (!file.exists()) { - return logic("picture file is not exists", echo) - } - NtV2RichMediaSvc.tryUploadResourceByNt( - chatType = chatType, - elementType = fileType, - resources = arrayListOf(file), - timeout = 30.seconds - ).onSuccess { - return ok(UploadResult(it.map { - CommFileInfo( - modeId = it.fileModelId, - fileName = it.fileName, - fileSize = it.fileSize, - md5 = it.md5, - uuid = it.uuid, - subId = it.subId, - sha = it.sha ?: "" - ) - }), echo) - }.onFailure { - return logic("upload failed: ${it.message ?: it.toString()}", echo) - } - } - return logic("upload failed", echo) - } - - override val requiredParams: Array = arrayOf("file") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadPrivateFile.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadPrivateFile.kt deleted file mode 100644 index ffe5b848..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadPrivateFile.kt +++ /dev/null @@ -1,154 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.media.MediaMetadataRetriever -import com.tencent.mobileqq.qroute.QRoute -import com.tencent.qqnt.kernel.nativeinterface.FileElement -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.msg.api.IMsgService -import com.tencent.qqnt.msg.api.IMsgUtilApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.withContext -import kotlinx.coroutines.withTimeoutOrNull -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.helper.TransfileHelper -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.qqinterface.servlet.transfile.RichMediaUploadHandler -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.utils.FileUtils -import moe.fuqiuluo.shamrock.utils.MD5 -import moe.fuqiuluo.symbols.OneBotHandler -import java.io.File -import java.io.FileOutputStream -import kotlin.coroutines.resume - -@OneBotHandler("upload_private_file") -internal object UploadPrivateFile : IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val userId = session.getLong("user_id") - val file = session.getString("file") - val name = session.getString("name") - .replace("/", "_") - .replace("\\", "_") - .replace("\n", "_") - .replace("\t", "_") - .replace("\r", "_") - return invoke(userId, file, name, session.echo) - } - - suspend operator fun invoke( - userId: Long, - file: String, - name: String, - echo: JsonElement = EmptyJsonString - ): String { - var srcFile = File(file) - if (!srcFile.exists()) { - srcFile = FileUtils.getFile(file) - } - - if (!srcFile.exists()) { - srcFile = file.let { - val md5 = it.replace( - regex = "[{}\\-]".toRegex(), - replacement = "" - ).split(".")[0].lowercase() - if (md5.length == 32) { - FileUtils.getFileByMd5(it) - } else { - FileUtils.parseAndSave(it) - } - } - } - - if (!srcFile.exists()) { - return badParam("文件不存在", echo) - } - - val fileElement = FileElement() - fileElement.fileMd5 = "" - fileElement.fileName = name - fileElement.filePath = srcFile.absolutePath - fileElement.fileSize = srcFile.length() - fileElement.folderId = srcFile.parent ?: "" - fileElement.picWidth = 0 - fileElement.picHeight = 0 - fileElement.videoDuration = 0 - fileElement.picThumbPath = HashMap() - fileElement.expireTime = 0L - fileElement.fileSha = "" - fileElement.fileSha3 = "" - fileElement.file10MMd5 = "" - when (TransfileHelper.getExtensionId(name)) { - 0 -> { - val wh = QRoute.api(IMsgUtilApi::class.java) - .getPicSizeByPath(srcFile.absolutePath) - fileElement.picWidth = wh.first - fileElement.picHeight = wh.second - fileElement.picThumbPath[750] = srcFile.absolutePath - } - 2 -> { - val thumbPic = FileUtils.getFileByMd5(MD5.genFileMd5Hex(srcFile.absolutePath)) - withContext(Dispatchers.IO) { - val fileOutputStream = FileOutputStream(thumbPic) - val retriever = MediaMetadataRetriever() - retriever.setDataSource(fileElement.filePath) - retriever.frameAtTime?.compress(Bitmap.CompressFormat.JPEG, 60, fileOutputStream) - fileOutputStream.flush() - fileOutputStream.close() - } - val options = BitmapFactory.Options() - BitmapFactory.decodeFile(thumbPic.absolutePath, options) - fileElement.picHeight = options.outHeight - fileElement.picWidth = options.outWidth - fileElement.picThumbPath = hashMapOf(750 to thumbPic.absolutePath) - } - } - val msgElement = MsgElement() - msgElement.elementType = MsgConstant.KELEMTYPEFILE - msgElement.fileElement = fileElement - - // 根据文件大小调整超时时间 - val msgIdPair = MessageHelper.generateMsgId(MsgConstant.KCHATTYPEC2C) - val info = (withTimeoutOrNull((srcFile.length() / (300 * 1024)) * 1000 + 5000) { - val msgService = QRoute.api(IMsgService::class.java) - val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, userId.toString()) - suspendCancellableCoroutine { - msgService.sendMsgWithMsgId( - contact, msgIdPair.qqMsgId, arrayListOf(msgElement) - ) { code, reason -> - if (code != 0) { - LogCenter.log("私聊文件消息发送异常(code = $code, reason = $reason)") - it.resume(null) - } - } - RichMediaUploadHandler.registerListener(msgIdPair.qqMsgId) { - it.resume(this) - return@registerListener true - } - } - } ?: return error("上传文件失败", echo)).also { - if (it.commonFileInfo == null) { - return error(it.fileErrMsg ?: "上传文件失败", echo) - } - }.commonFileInfo - - return ok(data = UploadGroupFile.FileUploadResult( - msgHash = msgIdPair.msgHashId, - bizid = info.bizType ?: 0, - md5 = info.md5, - sha = info.sha, - sha3 = info.sha3, - fileId = info.uuid - ), echo = echo) - } - - override val requiredParams: Array = arrayOf("user_id", "file", "name") -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/BDHWorker.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/BDHWorker.kt deleted file mode 100644 index 84d4513a..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/BDHWorker.kt +++ /dev/null @@ -1,50 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.api - -import android.util.Base64 -import com.tencent.mobileqq.transfile.TransferRequest -import com.tencent.mobileqq.transfile.api.ITransFileController -import io.ktor.server.routing.Routing -import io.ktor.server.routing.post -import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig -import moe.fuqiuluo.shamrock.remote.structures.Status -import moe.fuqiuluo.shamrock.tools.fetchPost -import moe.fuqiuluo.shamrock.tools.respond -import moe.fuqiuluo.shamrock.utils.MD5 -import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher -import mqq.app.MobileQQ -import kotlin.random.Random -import kotlin.random.nextLong - -fun Routing.registerBDH() { - if(ShamrockConfig.isDev()) post("/upload_group_image") { - val troop = fetchPost("troop") - val picBytes = Base64.decode(fetchPost("pic"), Base64.DEFAULT) - val md5Str = MD5.getMd5Hex(picBytes) - val file = MobileQQ.getContext().cacheDir.resolve("vas_ad").also { - if (!it.exists()) it.mkdir() - }.resolve("$md5Str.jpg") - file.writeBytes(picBytes) - val sender = fetchPost("sender") - - val runtime = AppRuntimeFetcher.appRuntime - val transferRequest = TransferRequest() - transferRequest.needSendMsg = false - transferRequest.mSelfUin = runtime.account - transferRequest.mPeerUin = troop - transferRequest.mSecondId = sender - transferRequest.mUinType = 1 - transferRequest.mFileType = 1 - transferRequest.mUniseq = Random.nextLong(10000L .. 1000000) - transferRequest.mIsUp = true - transferRequest.mBusiType = 1030 - transferRequest.mMd5 = md5Str - transferRequest.mLocalPath = file.absolutePath - val picUpExtraInfo = TransferRequest.PicUpExtraInfo() - picUpExtraInfo.mIsRaw = true - transferRequest.mPicSendSource = 8 - transferRequest.mExtraObj = picUpExtraInfo - (runtime.getRuntimeService(ITransFileController::class.java, "all") as ITransFileController) - .transferAsync(transferRequest) - respond(isOk = true, Status.Ok, "$md5Str.jpg") - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/FavouriteWeiyun.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/FavouriteWeiyun.kt deleted file mode 100644 index e4cb1850..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/FavouriteWeiyun.kt +++ /dev/null @@ -1,48 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.api - -import io.ktor.http.ContentType -import io.ktor.server.application.call -import io.ktor.server.response.respondText -import io.ktor.server.routing.Routing -import moe.fuqiuluo.shamrock.remote.action.handlers.FavAddImageMsg -import moe.fuqiuluo.shamrock.remote.action.handlers.FavAddTextMsg -import moe.fuqiuluo.shamrock.remote.action.handlers.FavGetItemContent -import moe.fuqiuluo.shamrock.remote.action.handlers.FavGetItemList -import moe.fuqiuluo.shamrock.tools.fetchOrNull -import moe.fuqiuluo.shamrock.tools.fetchOrThrow -import moe.fuqiuluo.shamrock.tools.getOrPost - -// fav.add_rich_media_msg - -fun Routing.fav() { - getOrPost("/fav/add_rich_media_msg") { - val uin = call.fetchOrThrow("user_id").toLong() - val nickName = call.fetchOrThrow("nick") - val time = call.fetchOrNull("time")?.toLong() ?: System.currentTimeMillis() - val content = call.fetchOrThrow("content") - val groupName = call.fetchOrNull("group_name") ?: "" - val groupId = call.fetchOrNull("group_id")?.toLong() ?: 0L - call.respondText(FavAddTextMsg(uin, nickName, time, content, groupName, groupId), ContentType.Application.Json) - } - - getOrPost("/fav/add_image_msg") { - val uin = call.fetchOrThrow("user_id").toLong() - val nickName = call.fetchOrThrow("nick") - val file = call.fetchOrThrow("file") - val groupName = call.fetchOrNull("groupName") ?: "" - val groupId = call.fetchOrNull("group_id")?.toLong() ?: 0L - call.respondText(FavAddImageMsg(uin, nickName, file, groupName, groupId), ContentType.Application.Json) - } - - getOrPost("/fav/get_item_content") { - val id = call.fetchOrThrow("id") - call.respondText(FavGetItemContent(id), ContentType.Application.Json) - } - - getOrPost("/fav/get_item_list") { - val category = call.fetchOrThrow("category").toInt() - val startPos = call.fetchOrThrow("start_pos").toInt() - val pageSize = call.fetchOrThrow("page_size").toInt() - call.respondText(FavGetItemList(category, startPos, pageSize), ContentType.Application.Json) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/FriendAction.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/FriendAction.kt deleted file mode 100644 index bb540e25..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/FriendAction.kt +++ /dev/null @@ -1,40 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.api - -import com.tencent.mobileqq.profilecard.api.IProfileCardBlacklistApi -import com.tencent.mobileqq.qroute.QRoute -import io.ktor.http.ContentType -import io.ktor.server.application.call -import io.ktor.server.response.respondText -import io.ktor.server.routing.Routing -import io.ktor.server.routing.get -import moe.fuqiuluo.shamrock.remote.action.handlers.GetFriendList -import moe.fuqiuluo.shamrock.remote.action.handlers.GetFriendSystemMsg -import moe.fuqiuluo.shamrock.remote.action.handlers.GetStrangerInfo -import moe.fuqiuluo.shamrock.remote.action.handlers.IsBlackListUin -import moe.fuqiuluo.shamrock.tools.fetchGetOrThrow -import moe.fuqiuluo.shamrock.tools.fetchOrNull -import moe.fuqiuluo.shamrock.tools.fetchOrThrow -import moe.fuqiuluo.shamrock.tools.getOrPost - -fun Routing.friendAction() { - getOrPost("/get_stranger_info") { - val userId = fetchOrThrow("user_id").toLong() - call.respondText(GetStrangerInfo(userId), ContentType.Application.Json) - } - - getOrPost("/get_friend_list") { - val refresh = fetchOrNull("no_cache")?.toBooleanStrict() - ?: fetchOrNull("refresh")?.toBooleanStrict() ?: false - call.respondText(GetFriendList(refresh), ContentType.Application.Json) - } - - getOrPost("/is_blacklist_uin") { - val userId = fetchOrThrow("user_id").toLong() - call.respondText(IsBlackListUin(userId), ContentType.Application.Json) - } - - getOrPost("/get_friend_system_msg") { - call.respondText(GetFriendSystemMsg(), ContentType.Application.Json) - } - -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/GenerateQSign.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/GenerateQSign.kt deleted file mode 100644 index ff7af368..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/GenerateQSign.kt +++ /dev/null @@ -1,437 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) - -package moe.fuqiuluo.shamrock.remote.api - -import android.app.ActivityManager -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.os.Process -import com.tencent.mobileqq.qsec.qsecdandelionsdk.Dandelion -import com.tencent.qphone.base.util.BaseApplication -import io.ktor.server.application.ApplicationCall -import io.ktor.server.application.call -import io.ktor.server.response.respond -import io.ktor.server.response.respondText -import io.ktor.server.routing.Routing -import io.ktor.server.routing.get -import io.ktor.server.routing.post -import io.ktor.server.routing.route -import io.ktor.util.pipeline.PipelineContext -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.withTimeoutOrNull -import kotlinx.io.core.BytePacketBuilder -import kotlinx.io.core.readBytes -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.shamrock.remote.structures.Status -import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY -import moe.fuqiuluo.shamrock.tools.EmptyJsonObject -import moe.fuqiuluo.shamrock.tools.fetchGetOrThrow -import moe.fuqiuluo.shamrock.tools.fetchOrNull -import moe.fuqiuluo.shamrock.tools.fetchOrThrow -import moe.fuqiuluo.shamrock.tools.fetchPostOrThrow -import moe.fuqiuluo.shamrock.tools.getOrPost -import moe.fuqiuluo.shamrock.tools.hex2ByteArray -import moe.fuqiuluo.shamrock.tools.json -import moe.fuqiuluo.shamrock.tools.respond -import moe.fuqiuluo.shamrock.tools.toHexString -import moe.fuqiuluo.shamrock.xposed.ipc.ShamrockIpc -import moe.fuqiuluo.shamrock.xposed.ipc.bytedata.IByteData -import moe.fuqiuluo.shamrock.xposed.ipc.qsign.IQSigner -import mqq.app.MobileQQ -import java.nio.ByteBuffer - -private var signer: IQSigner? = null -private var byteData: IByteData? = null - -private fun getMsfServiceInfo(): ActivityManager.RunningServiceInfo? { - val context = MobileQQ.getContext() - val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager - val runningServices = activityManager.getRunningServices(Int.MAX_VALUE) - for (serviceInfo in runningServices) { - val serviceName = serviceInfo.service.className - if (serviceName == "com.tencent.mobileqq.msf.service.MsfService") { - return serviceInfo - } - } - return null -} - -private fun isMsfServiceAlive(): Boolean { - return getMsfServiceInfo() != null -} - -fun Routing.qsign() { - getOrPost("/reset_qsign") { - val context = MobileQQ.getContext() - val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager - val runningServices = activityManager.getRunningServices(Int.MAX_VALUE) - - for (serviceInfo in runningServices) { - val serviceName = serviceInfo.service.className - if (serviceName == "com.tencent.mobileqq.msf.service.MsfService") { - Process.killProcess(serviceInfo.pid) - } - } - - GlobalScope.launch(Dispatchers.Main) { - val componentName = ComponentName(BaseApplication.getContext().packageName, "com.tencent.mobileqq.msf.service.MsfService") - val intent = Intent() - intent.component = componentName - intent.putExtra("to_SenderProcessName", "com.tencent.mobileqq") - BaseApplication.getContext().startService(intent) - } - - call.respond(OldApiResult(0, "重新启动MSF", data = EmptyJsonObject)) - } - - getOrPost("/get_running_service") { - val context = MobileQQ.getContext() - val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager - val runningServices = activityManager.getRunningServices(Int.MAX_VALUE) - val output = mutableListOf() - for (serviceInfo in runningServices) { - val serviceName = serviceInfo.service.className - val pid = serviceInfo.pid - val uid = serviceInfo.uid - output.add(mapOf( - "service" to serviceName, - "pid" to pid, - "uid" to uid - ).json) - } - call.respondText(output.json.toString()) - } - - get("/get_cmd_whitelist") { - if (!isMsfServiceAlive()) { - call.respond(OldApiResult(-2, "MSF服务未启动", null)) - return@get - } - if (signer == null || signer?.asBinder()?.isBinderAlive == false) { - if (!initSigner()) { - respond(false, Status.InternalHandlerError) - return@get - } - } - val list = signer!!.cmdWhiteList - call.respond(OldApiResult(0, "success", list)) - } - - getOrPost("/get_xw_debug_id") { - if (!isMsfServiceAlive()) { - call.respond(OldApiResult(-2, "MSF服务未启动", null)) - return@getOrPost - } - if (signer == null || signer?.asBinder()?.isBinderAlive == false) { - if (!initSigner()) { - respond(false, Status.InternalHandlerError) - return@getOrPost - } - } - - val uin = fetchOrThrow("uin") - val data = fetchOrThrow("data") - - lateinit var start: String - lateinit var end: String - - data.split("_").let { - start = it[0] - end = it[1] - } - val xwDebugId = signer!!.xwDebugId(uin, start, end) - - call.respond(OldApiResult(0, "success", xwDebugId.toHexString())) - } - - route("/sign") { - get { - val uin = fetchGetOrThrow("uin") - val cmd = fetchGetOrThrow("cmd") - val seq = fetchGetOrThrow("seq").toInt() - val buffer = fetchGetOrThrow("buffer").hex2ByteArray() - - requestSign(cmd, uin, seq, buffer) - } - post { - val uin = fetchPostOrThrow("uin") - val cmd = fetchPostOrThrow("cmd") - val seq = fetchPostOrThrow("seq").toInt() - val buffer = fetchPostOrThrow("buffer").hex2ByteArray() - - requestSign(cmd, uin, seq, buffer) - } - } - - get("/custom_energy") { - val data = fetchGetOrThrow("data") - val salt = fetchGetOrThrow("salt").hex2ByteArray() - - val sign = Dandelion.getInstance().fly(data, salt) - call.respond(OldApiResult(0, "success", sign.toHexString())) - } - - route("/energy") { - get { - val data = fetchGetOrThrow("data") - if(!(data.startsWith("810_") || data.startsWith("812_"))) { - call.respond(OldApiResult(-2, "data参数不合法", null)) - return@get - } - - val uin = fetchOrThrow("uin") - val salt = fetchSalt(data, uin) - if (salt.isEmpty()) { - call.respond(OldApiResult(-2, "无法自动决断mode,请主动提供", null)) - return@get - } - - val sign = Dandelion.getInstance().fly(data, salt) - call.respond(OldApiResult(0, "success", sign.toHexString())) - } - post { - val data = fetchPostOrThrow("data") - if(!(data.startsWith("810_") || data.startsWith("812_"))) { - call.respond(OldApiResult(-2, "data参数不合法", null)) - return@post - } - val uin = fetchOrThrow("uin") - val salt = fetchSalt(data, uin) - if (salt.isEmpty()) { - call.respond(OldApiResult(-2, "无法自动决断mode,请主动提供", null)) - return@post - } - - val sign = Dandelion.getInstance().fly(data, salt) - call.respond(OldApiResult(0, "success", sign.toHexString())) - } - } - - get("/get_byte") { - if (!isMsfServiceAlive()) { - call.respond(OldApiResult(-2, "MSF服务未启动", null)) - return@get - } - if (byteData == null || byteData?.asBinder()?.isBinderAlive == false) { - val binder = ShamrockIpc.get(ShamrockIpc.IPC_BYTEDATA) - if (binder == null) { - call.respond(OldApiResult(-2, "获取失败", null)) - return@get - } else { - byteData = IByteData.Stub.asInterface(binder) - binder.linkToDeath({ - byteData = null - }, 0) - } - } - - val data = fetchGetOrThrow("data") - if(!(data.startsWith("810_") || data.startsWith("812_"))) { - call.respond(OldApiResult(-2, "data参数不合法", null)) - return@get - } - - val uin = fetchOrThrow("uin") - val salt = fetchSalt(data, uin) - if (salt.isEmpty()) { - call.respond(OldApiResult(-2, "无法自动决断mode,请主动提供", null)) - return@get - } - - val sign = byteData!!.sign(uin, data, salt).sign - - if (sign == null) { - call.respond(OldApiResult(-2, "获取失败", null)) - } else { - call.respond(OldApiResult(0, "success", sign.toHexString())) - } - } - - get("/friend_sign") { - if (!isMsfServiceAlive()) { - call.respond(OldApiResult(-2, "MSF服务未启动", null)) - return@get - } - if (signer == null || signer?.asBinder()?.isBinderAlive == false) { - if (!initSigner()) { - respond(false, Status.InternalHandlerError) - return@get - } - } - - val addUin = fetchOrThrow("add_uin") - val source = fetchOrThrow("source") - val uin = fetchOrThrow("uin").toLong() - - val sign = signer!!.energy("add_friend", BytePacketBuilder().also { - it.writeLong(uin) - it.writeLong(addUin.toLong()) - it.writeInt(source.toInt()) - }.build().readBytes()) - if (sign == null) { - call.respond(OldApiResult(-1, "failed", null)) - } else { - call.respond(OldApiResult(0, "success", sign.toHexString())) - } - } - - get("/group_sign") { - if (!isMsfServiceAlive()) { - call.respond(OldApiResult(-2, "MSF服务未启动", null)) - return@get - } - if (signer == null || signer?.asBinder()?.isBinderAlive == false) { - if (!initSigner()) { - respond(false, Status.InternalHandlerError) - return@get - } - } - - val addUin = fetchOrThrow("group_uin") - val source = fetchOrThrow("source") - val subsource = fetchOrThrow("sub_source") - val uin = fetchOrThrow("uin").toLong() - - val sign = signer!!.energy("add_group", BytePacketBuilder().also { - it.writeLong(uin) - it.writeLong(addUin.toLong()) - it.writeInt(source.toInt()) - it.writeInt(subsource.toInt()) - }.build().readBytes()) - if (sign == null) { - call.respond(OldApiResult(-1, "failed", null)) - } else { - call.respond(OldApiResult(0, "success", sign.toHexString())) - } - } -} - -private suspend inline fun PipelineContext.fetchSalt( - data: String, - uin: String -): ByteArray { - var mode = fetchOrNull("mode") - if (mode == null) { - mode = when(data) { - "810_d", "810_a", "810_f", "810_9" -> "v2" - "810_2", "810_25", "810_7", "810_24" -> "v1" - "812_b", "812_a" -> "v3" - "812_5" -> "v4" - else -> null - } - } - if (mode == null) { - return EMPTY_BYTE_ARRAY - } - - val version = fetchOrThrow("version") - if (!version.startsWith("6.0.0")) { - throw RuntimeException("version参数应该是6.0.0开头") - } - - return when (mode) { - "v1" -> { - val guid = fetchOrThrow("guid").hex2ByteArray() - val salt = ByteBuffer.allocate(8 + 2 + guid.size + 2 + 10 + 4) - val sub = data.substring(4).toInt(16) - salt.putLong(uin.toLong()) - salt.putShort(guid.size.toShort()) - salt.put(guid) - salt.putShort(version.length.toShort()) - salt.put(version.toByteArray()) - salt.putInt(sub) - salt.array() - } - "v2" -> { - val guid = fetchOrThrow("guid").hex2ByteArray() - val sub = data.substring(4).toInt(16) - val salt = ByteBuffer.allocate(4 + 2 + guid.size + 2 + 10 + 4 + 4) - salt.putInt(0) - salt.putShort(guid.size.toShort()) - salt.put(guid) - salt.putShort(version.length.toShort()) - salt.put(version.toByteArray()) - salt.putInt(sub) - salt.putInt(0) - salt.array() - } - "v3" -> { // 812_a - val phone = fetchOrThrow("phone").toByteArray() // 86-xxx - val salt = ByteBuffer.allocate(phone.size + 2 + 2 + version.length + 2) - salt.put(phone) - //println(String(phone)) - salt.putShort(0) - salt.putShort(version.length.toShort()) - salt.put(version.toByteArray()) - salt.putShort(0) - salt.array() - } - "v4" -> { // 812_5 - error("Not support [v4] mode.") - } - else -> EMPTY_BYTE_ARRAY - } -} - -@Serializable -private data class Sign( - val token: String, - val extra: String, - val sign: String, - val o3did: String, - val requestCallback: List -) - -private suspend fun initSigner(): Boolean { - if (!isMsfServiceAlive()) { - return false - } - val binder = ShamrockIpc.get(ShamrockIpc.IPC_QSIGN) - if (binder == null) { - //respond(false, Status.InternalHandlerError) - return false - } else { - signer = IQSigner.Stub.asInterface(binder) - binder.linkToDeath({ - signer = null - }, 0) - return true - } -} - -private suspend fun PipelineContext.requestSign( - cmd: String, - uin: String, - seq: Int, - buffer: ByteArray, -) { - if (!isMsfServiceAlive()) { - call.respond(OldApiResult(-2, "MSF服务未启动", null)) - return - } - if (signer == null || signer?.asBinder()?.isBinderAlive == false) { - if (!initSigner()) { - respond(false, Status.InternalHandlerError) - return - } - } - - val sign = withTimeoutOrNull(5000) { - signer!!.sign(cmd, seq, uin, buffer) - } ?: run { - respond(false, Status.IAmTired) - return - } - - call.respond(OldApiResult(0, "success", Sign( - sign.token.toHexString(), - sign.extra.toHexString(), - sign.sign.toHexString(), "", listOf() - ))) -} - diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/GetFrameworkInfo.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/GetFrameworkInfo.kt deleted file mode 100644 index 1e6fa68c..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/GetFrameworkInfo.kt +++ /dev/null @@ -1,27 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.api - -import io.ktor.server.routing.Routing -import io.ktor.server.routing.get -import kotlinx.coroutines.delay -import moe.fuqiuluo.shamrock.remote.structures.Status -import moe.fuqiuluo.shamrock.tools.getOrPost -import moe.fuqiuluo.shamrock.tools.respond -import moe.fuqiuluo.shamrock.helper.LogCenter -import kotlin.system.exitProcess - -fun Routing.obtainFrameworkInfo() { - getOrPost("/get_start_time") { - respond( - isOk = true, - code = Status.Ok, - moe.fuqiuluo.shamrock.remote.HTTPServer.startTime - ) - } - - get("/shut") { - moe.fuqiuluo.shamrock.remote.HTTPServer.stop() - LogCenter.log("正在关闭Shamrock。", toast = true) - delay(3000) - exitProcess(0) - } -} diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/GroupAction.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/GroupAction.kt deleted file mode 100644 index 5526c89f..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/GroupAction.kt +++ /dev/null @@ -1,174 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.api - -import io.ktor.http.ContentType -import io.ktor.server.application.call -import io.ktor.server.response.respondText -import io.ktor.server.routing.Routing -import moe.fuqiuluo.shamrock.remote.action.handlers.* -import moe.fuqiuluo.shamrock.tools.fetchGetOrNull -import moe.fuqiuluo.shamrock.tools.fetchOrNull -import moe.fuqiuluo.shamrock.tools.fetchOrThrow -import moe.fuqiuluo.shamrock.tools.getOrPost - -fun Routing.troopAction() { - getOrPost("/set_group_comment_face") { - val groupId = fetchOrThrow("group_id").toLong() - val msgId = fetchOrNull("msg_id")?.toIntOrNull() ?: fetchOrThrow("message_id").toInt() - val faceId = fetchOrThrow("face_id").toInt() - val isSet = fetchGetOrNull("is_set") ?: "true" - call.respondText(SetGroupCommentFace(groupId, msgId, faceId, when(isSet) { - "true" -> true - "false" -> false - "1" -> true - "0" -> false - else -> true - }), ContentType.Application.Json) - } - - getOrPost("/get_not_joined_group_info") { - val groupId = fetchOrThrow("group_id").toLong() - call.respondText(GetNotJoinedGroupInfo(groupId), ContentType.Application.Json) - } - - getOrPost("/get_prohibited_member_list") { - val groupId = fetchOrThrow("group_id").toLong() - call.respondText(GetProhibitedMemberList(groupId), ContentType.Application.Json) - } - - getOrPost("/group_touch") { - val groupId = fetchOrThrow("group_id").toLong() - val userId = fetchOrThrow("user_id").toLong() - call.respondText(GroupPoke(groupId, userId), ContentType.Application.Json) - } - - getOrPost("/get_group_honor_info") { - val groupId = fetchOrThrow("group_id").toLong() - val refresh = fetchOrNull("no_cache")?.toBooleanStrict() - ?: fetchOrNull("refresh")?.toBooleanStrict() ?: false - call.respondText(GetTroopHonor(groupId, refresh), ContentType.Application.Json) - } - - getOrPost("/get_group_member_list") { - val groupId = fetchOrThrow("group_id").toLong() - val refresh = fetchOrNull("no_cache")?.toBooleanStrict() - ?: fetchOrNull("refresh")?.toBooleanStrict() ?: false - call.respondText(GetTroopMemberList(groupId, refresh), ContentType.Application.Json) - } - - getOrPost("/get_group_member_info") { - val groupId = fetchOrThrow("group_id").toLong() - val userId = fetchOrThrow("user_id").toLong() - val refresh = fetchOrNull("no_cache")?.toBooleanStrict() - ?: fetchOrNull("refresh")?.toBooleanStrict() ?: false - call.respondText(GetTroopMemberInfo(groupId, userId, refresh), ContentType.Application.Json) - } - - getOrPost("/get_group_list") { - val refresh = fetchOrNull("no_cache")?.toBooleanStrict() - ?: fetchOrNull("refresh")?.toBooleanStrict() ?: true - call.respondText(GetTroopList(refresh), ContentType.Application.Json) - } - - getOrPost("/get_group_info") { - val groupId = fetchOrThrow("group_id").toLong() - val refresh = fetchOrNull("no_cache")?.toBooleanStrict() - ?: fetchOrNull("refresh")?.toBooleanStrict() ?: false - call.respondText(GetTroopInfo(groupId, refresh), ContentType.Application.Json) - } - - getOrPost("/set_group_special_title") { - val groupId = fetchOrThrow("group_id").toLong() - val userId = fetchOrThrow("user_id").toLong() - val title = fetchOrThrow("special_title") - call.respondText(SetGroupUnique(groupId, userId, title), ContentType.Application.Json) - } - - getOrPost("/set_group_name") { - val groupId = fetchOrThrow("group_id").toLong() - val card = fetchOrThrow("group_name") - call.respondText(ModifyTroopName(groupId, card), ContentType.Application.Json) - } - - getOrPost("/set_group_card") { - val groupId = fetchOrThrow("group_id").toLong() - val userId = fetchOrThrow("user_id").toLong() - val card = fetchOrNull("card") ?: "" - call.respondText(ModifyTroopMemberName(groupId, userId, card), ContentType.Application.Json) - } - - getOrPost("/set_group_admin") { - val groupId = fetchOrThrow("group_id").toLong() - val userId = fetchOrThrow("user_id").toLong() - val enable = fetchOrThrow("enable").toBooleanStrict() - call.respondText(SetGroupAdmin(groupId, userId, enable), ContentType.Application.Json) - } - - getOrPost("/set_group_whole_ban") { - val groupId = fetchOrThrow("group_id").toLong() - val enable = fetchOrThrow("enable").toBooleanStrict() - call.respondText(SetGroupWholeBan(groupId, enable), ContentType.Application.Json) - } - - getOrPost("/set_group_ban") { - val groupId = fetchOrThrow("group_id").toLong() - val userId = fetchOrThrow("user_id").toLong() - val duration = fetchOrNull("duration")?.toInt() ?: (30 * 60) - - call.respondText(BanTroopMember(groupId, userId, duration), ContentType.Application.Json) - } - - getOrPost("/set_group_kick") { - val userId = fetchOrThrow("user_id").toLong() - val groupId = fetchOrThrow("group_id").toLong() - val kickMsg = fetchOrNull("kick_msg") ?: "" - val rejectAddRequest = when(fetchGetOrNull("reject_add_request")) { - "1", "true" -> true - else -> false - } - call.respondText(KickTroopMember(groupId, userId, rejectAddRequest, kickMsg), ContentType.Application.Json) - } - - getOrPost("/set_essence_msg") { - val messageId = fetchOrThrow("message_id").toInt() - call.respondText(SetEssenceMessage(messageId), ContentType.Application.Json) - } - - getOrPost("/delete_essence_msg") { - val messageId = fetchOrThrow("message_id").toInt() - call.respondText(DeleteEssenceMessage(messageId), ContentType.Application.Json) - } - - getOrPost("/get_essence_msg_list") { - val groupId = fetchOrThrow("group_id").toLong() - val page = fetchOrNull("page")?.toIntOrNull() ?: 0 - val pageSize = fetchOrNull("page_size")?.toIntOrNull() ?: 20 - call.respondText(GetEssenceMessageList(groupId, page, pageSize), ContentType.Application.Json) - } - - getOrPost("/get_group_system_msg") { - call.respondText(GetGroupSystemMsg(), ContentType.Application.Json) - } - - getOrPost("/_get_group_notice") { - val groupId = fetchOrThrow("group_id").toLong() - call.respondText(GetGroupNotice(groupId), ContentType.Application.Json) - } - - getOrPost("/_send_group_notice") { - val groupId = fetchOrThrow("group_id").toLong() - val text = fetchOrThrow("content") - val image = fetchOrNull("image") - call.respondText(SendGroupNotice(groupId, text, image), ContentType.Application.Json) - } - - getOrPost("/send_group_sign") { - val groupId = fetchOrThrow("group_id").toLong() - call.respondText(SendGroupSign(groupId), ContentType.Application.Json) - } - - getOrPost("/get_group_at_all_remain") { - val groupId = fetchOrThrow("group_id").toLong() - call.respondText(GetGroupRemainAtAllRemain(groupId), ContentType.Application.Json) - } - -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/GuildAction.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/GuildAction.kt deleted file mode 100644 index 7140b658..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/GuildAction.kt +++ /dev/null @@ -1,171 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.api - -import io.ktor.http.ContentType -import io.ktor.server.application.call -import io.ktor.server.response.respondText -import io.ktor.server.routing.Routing -import io.ktor.server.routing.get -import io.ktor.server.routing.post -import io.ktor.server.routing.route -import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.remote.action.handlers.CreateGuildRole -import moe.fuqiuluo.shamrock.remote.action.handlers.DeleteGuildRole -import moe.fuqiuluo.shamrock.remote.action.handlers.GetGProChannelList -import moe.fuqiuluo.shamrock.remote.action.handlers.GetGuildFeeds -import moe.fuqiuluo.shamrock.remote.action.handlers.GetGuildList -import moe.fuqiuluo.shamrock.remote.action.handlers.GetGuildMemberList -import moe.fuqiuluo.shamrock.remote.action.handlers.GetGuildMemberProfile -import moe.fuqiuluo.shamrock.remote.action.handlers.GetGuildMetaByGuest -import moe.fuqiuluo.shamrock.remote.action.handlers.GetGuildRoles -import moe.fuqiuluo.shamrock.remote.action.handlers.GetGuildServiceProfile -import moe.fuqiuluo.shamrock.remote.action.handlers.SendGuildMessage -import moe.fuqiuluo.shamrock.remote.action.handlers.SendMessage -import moe.fuqiuluo.shamrock.remote.action.handlers.SetGuildMemberRole -import moe.fuqiuluo.shamrock.remote.action.handlers.UpdateGuildRole -import moe.fuqiuluo.shamrock.tools.fetchGetOrNull -import moe.fuqiuluo.shamrock.tools.fetchGetOrThrow -import moe.fuqiuluo.shamrock.tools.fetchOrNull -import moe.fuqiuluo.shamrock.tools.fetchOrThrow -import moe.fuqiuluo.shamrock.tools.fetchPostJsonArray -import moe.fuqiuluo.shamrock.tools.fetchPostJsonObject -import moe.fuqiuluo.shamrock.tools.fetchPostJsonString -import moe.fuqiuluo.shamrock.tools.fetchPostOrNull -import moe.fuqiuluo.shamrock.tools.fetchPostOrThrow -import moe.fuqiuluo.shamrock.tools.getOrPost -import moe.fuqiuluo.shamrock.tools.isJsonData -import moe.fuqiuluo.shamrock.tools.isJsonObject -import moe.fuqiuluo.shamrock.tools.isJsonString -import moe.fuqiuluo.shamrock.tools.jsonArray - -fun Routing.guildAction() { - getOrPost("/get_guild_service_profile") { - call.respondText(GetGuildServiceProfile(), ContentType.Application.Json) - } - - getOrPost("/get_guild_list") { - val refresh = fetchGetOrNull("refresh") ?: fetchOrNull("no_cache") - call.respondText(GetGuildList(refresh?.toBoolean() ?: false, false), ContentType.Application.Json) - } - - getOrPost("/get_guild_member_list") { - val guildId = fetchOrThrow("guild_id") - val all = fetchGetOrNull("all")?.toBoolean() ?: false - call.respondText(GetGuildMemberList(guildId.toULong(), all, fetchOrNull("next_token") ?: ""), ContentType.Application.Json) - } - - getOrPost("/get_guild_meta_by_guest") { - val guildId = fetchOrThrow("guild_id") - call.respondText(GetGuildMetaByGuest(guildId.toULong()), ContentType.Application.Json) - } - - getOrPost("/get_guild_channel_list") { - val guildId = fetchOrThrow("guild_id") - val refresh = fetchGetOrNull("refresh") ?: fetchOrNull("no_cache") - call.respondText(GetGProChannelList(guildId.toULong(), refresh?.toBoolean() ?: false), ContentType.Application.Json) - } - - getOrPost("/get_guild_member_profile") { - val guildId = fetchOrThrow("guild_id") - val userId = fetchOrThrow("user_id") - call.respondText(GetGuildMemberProfile(guildId.toULong(), userId.toULong()), ContentType.Application.Json) - } - - route("/(send_guild_channel_msg|send_guild_message|send_guild_msg)".toRegex()) { - get { - val guildId = fetchGetOrThrow("guild_id").toULong() - val channelId = fetchGetOrThrow("channel_id").toULong() - val message = fetchGetOrThrow("message") - val autoEscape = fetchGetOrNull("auto_escape")?.toBoolean() ?: false - val retryCnt = fetchGetOrNull("retry_cnt")?.toInt() ?: 3 - val recallDuration = fetchGetOrNull("recall_duration")?.toLong() - call.respondText(SendGuildMessage(guildId, channelId, message, autoEscape, retryCnt, recallDuration), ContentType.Application.Json) - } - post { - val retryCnt = fetchOrNull("retry_cnt")?.toInt() ?: 3 - val recallDuration = fetchOrNull("recall_duration")?.toLong() - val guildId = fetchOrThrow("guild_id").toULong() - val channelId = fetchOrThrow("channel_id").toULong() - call.respondText(if (isJsonData() && !isJsonString("message")) { - if (isJsonObject("message")) { - SendGuildMessage( - guildId = guildId, - channelId = channelId, - message = listOf(fetchPostJsonObject("message")).jsonArray, - retryCnt = retryCnt, - recallDuration = recallDuration - ) - } else { - SendGuildMessage( - guildId = guildId, - channelId = channelId, - message = fetchPostJsonArray("message"), - retryCnt = retryCnt, - recallDuration = recallDuration - ) - } - } else { - val autoEscape = fetchPostOrNull("auto_escape")?.toBooleanStrict() ?: false - SendGuildMessage( - guildId = guildId, - channelId = channelId, - message = fetchOrThrow("message"), - autoEscape = autoEscape, - retryCnt = retryCnt, - recallDuration = recallDuration - ) - }, ContentType.Application.Json) - } - } - - getOrPost("/get_guild_feeds") { - val guildId = fetchOrThrow("guild_id").toULong() - val channelId = fetchOrNull("channel_id")?.toULong() ?: 0uL - val from = fetchOrNull("from")?.toInt() ?: 0 - call.respondText(GetGuildFeeds(guildId, channelId, from), ContentType.Application.Json) - } - - getOrPost("/get_guild_roles") { - val guildId = fetchOrThrow("guild_id").toULong() - call.respondText(GetGuildRoles(guildId), ContentType.Application.Json) - } - - getOrPost("/delete_guild_role") { - val guildId = fetchOrThrow("guild_id").toULong() - val roleId = fetchOrThrow("role_id").toULong() - call.respondText(DeleteGuildRole(guildId, roleId), ContentType.Application.Json) - } - - getOrPost("/set_guild_member_role") { - val guildId = fetchOrThrow("guild_id").toULong() - val roleId = fetchOrThrow("role_id").toULong() - val set = fetchOrNull("set")?.toBoolean() ?: false - val userId = fetchOrNull("user_id")?.toULong() - val users = fetchOrNull("users")?.split(",")?.map { it.toULong() } - call.respondText( - if (userId != null) { - SetGuildMemberRole(guildId, userId, roleId, set) - } else if (users != null) { - SetGuildMemberRole(guildId, users, roleId, set) - } else { - throw IllegalArgumentException("missing user_id or users") - }, - ContentType.Application.Json - ) - } - - getOrPost("/update_guild_role") { - val guildId = fetchOrThrow("guild_id").toULong() - val roleId = fetchOrThrow("role_id").toULong() - val name = fetchOrThrow("name") - val color = fetchOrThrow("color").toLong() - call.respondText(UpdateGuildRole(guildId, roleId, name, color), ContentType.Application.Json) - } - - getOrPost("/create_guild_role") { - val guildId = fetchOrThrow("guild_id").toULong() - val name = fetchOrThrow("name") - val color = fetchOrThrow("color").toLong() - val initialUsers = fetchOrThrow("initial_users").split(",").map { it.toULong() } - call.respondText(CreateGuildRole(guildId, color, name, initialUsers), ContentType.Application.Json) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/LogExplorer.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/LogExplorer.kt deleted file mode 100644 index a74c7309..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/LogExplorer.kt +++ /dev/null @@ -1,17 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.api - -import io.ktor.server.application.call -import io.ktor.server.response.respondText -import io.ktor.server.routing.Routing -import moe.fuqiuluo.shamrock.tools.fetchOrNull -import moe.fuqiuluo.shamrock.tools.getOrPost -import moe.fuqiuluo.shamrock.helper.LogCenter - -fun Routing.showLog() { - getOrPost("/log") { - val start = fetchOrNull("start")?.toIntOrNull() ?: 0 - val recent =fetchOrNull("recent")?.toBooleanStrictOrNull() ?: false - val log = LogCenter.getLogLines(start, recent) - call.respondText(log.joinToString("\n")) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/MainRoute.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/MainRoute.kt deleted file mode 100644 index 59c46453..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/MainRoute.kt +++ /dev/null @@ -1,111 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.api - -import io.ktor.http.ContentType -import io.ktor.server.application.ApplicationCall -import io.ktor.server.application.call -import io.ktor.server.request.httpVersion -import io.ktor.server.response.respondText -import io.ktor.server.routing.Routing -import io.ktor.server.routing.get -import io.ktor.server.routing.post -import io.ktor.server.routing.route -import io.ktor.util.pipeline.PipelineContext -import kotlinx.serialization.Contextual -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.jsonObject -import moe.fuqiuluo.shamrock.remote.HTTPServer -import moe.fuqiuluo.shamrock.remote.action.ActionManager -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.config.ECHO_KEY -import moe.fuqiuluo.shamrock.remote.structures.EmptyObject -import moe.fuqiuluo.shamrock.remote.structures.IndexData -import moe.fuqiuluo.shamrock.remote.structures.Status -import moe.fuqiuluo.shamrock.tools.EmptyJsonObject -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.tools.asJsonObjectOrNull -import moe.fuqiuluo.shamrock.tools.asString -import moe.fuqiuluo.shamrock.tools.fetchOrNull -import moe.fuqiuluo.shamrock.tools.fetchOrThrow -import moe.fuqiuluo.shamrock.tools.fetchPostJsonElement -import moe.fuqiuluo.shamrock.tools.fetchPostJsonElementOrNull -import moe.fuqiuluo.shamrock.tools.fetchPostJsonObjectOrNull -import moe.fuqiuluo.shamrock.tools.isJsonArray -import moe.fuqiuluo.shamrock.tools.isJsonObject -import moe.fuqiuluo.shamrock.tools.json -import moe.fuqiuluo.shamrock.tools.respond -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import mqq.app.MobileQQ - -@Serializable -data class OldApiResult( - val code: Int, - val msg: String = "", - @Contextual - val data: T? = null -) - -suspend fun PipelineContext.handleAsJsonObject(data: JsonObject) { - val action = data["action"].asString - val echo = data["echo"] ?: EmptyJsonString - call.attributes.put(ECHO_KEY, echo) - - val params = data["params"].asJsonObjectOrNull ?: EmptyJsonObject - - val handler = ActionManager[action] - if (handler == null) { - respond(false, Status.UnsupportedAction, EmptyObject, "不支持的Action", echo = echo) - } else { - call.respondText(handler.handle(ActionSession(params, echo)), ContentType.Application.Json) - } -} - -suspend fun PipelineContext.handleAsJsonArray(data: JsonArray) { - data.forEach { - when (it) { - is JsonArray -> handleAsJsonArray(it) - is JsonObject -> handleAsJsonObject(it) - else -> handleAsJsonObject(it.jsonObject) - } - } -} - -fun Routing.echoVersion() { - route("/") { - get { - respond( - isOk = true, - code = Status.Ok, - data = IndexData(PlatformUtils.getClientVersion(MobileQQ.getContext()), HTTPServer.startTime, call.request.httpVersion) - ) - } - post { - fetchPostJsonElementOrNull()?.let { - if (it is JsonArray) { - handleAsJsonArray(it) - return@post - } else if (it is JsonObject) { - handleAsJsonObject(it) - return@post - } - } - val action = fetchOrThrow("action") - val echo = if (isJsonObject("echo") || isJsonArray("echo")) { - fetchPostJsonElement("echo") - } else { - (fetchOrNull("echo") ?: "").json - } - call.attributes.put(ECHO_KEY, echo) - - val params = fetchPostJsonObjectOrNull("params") ?: EmptyJsonObject - - val handler = ActionManager[action] - if (handler == null) { - respond(false, Status.UnsupportedAction, EmptyObject, "不支持的Action", echo = echo) - } else { - call.respondText(handler.handle(ActionSession(params, echo)), ContentType.Application.Json) - } - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/MessageAction.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/MessageAction.kt deleted file mode 100644 index 983687b9..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/MessageAction.kt +++ /dev/null @@ -1,385 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.api - -import moe.fuqiuluo.shamrock.helper.MessageHelper -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import io.ktor.http.ContentType -import io.ktor.server.application.call -import io.ktor.server.response.respondText -import io.ktor.server.routing.Routing -import io.ktor.server.routing.get -import io.ktor.server.routing.post -import io.ktor.server.routing.route -import moe.fuqiuluo.shamrock.helper.db.MessageDB -import moe.fuqiuluo.shamrock.remote.action.handlers.* -import moe.fuqiuluo.shamrock.remote.structures.Status -import moe.fuqiuluo.shamrock.tools.fetchGetOrNull -import moe.fuqiuluo.shamrock.tools.fetchGetOrThrow -import moe.fuqiuluo.shamrock.tools.fetchOrNull -import moe.fuqiuluo.shamrock.tools.fetchOrThrow -import moe.fuqiuluo.shamrock.tools.fetchPostJsonArray -import moe.fuqiuluo.shamrock.tools.fetchPostJsonElement -import moe.fuqiuluo.shamrock.tools.fetchPostJsonObject -import moe.fuqiuluo.shamrock.tools.fetchPostJsonString -import moe.fuqiuluo.shamrock.tools.fetchPostOrNull -import moe.fuqiuluo.shamrock.tools.fetchPostOrThrow -import moe.fuqiuluo.shamrock.tools.getOrPost -import moe.fuqiuluo.shamrock.tools.isJsonArray -import moe.fuqiuluo.shamrock.tools.isJsonData -import moe.fuqiuluo.shamrock.tools.isJsonObject -import moe.fuqiuluo.shamrock.tools.isJsonString -import moe.fuqiuluo.shamrock.tools.jsonArray -import moe.fuqiuluo.shamrock.tools.respond - -fun Routing.messageAction() { - route("/adapt_share_json") { - get { - val cover = fetchGetOrThrow("cover") - val desc = fetchGetOrThrow("desc") - val url = fetchGetOrNull("url") ?: "" - call.respondText(AdaptShareJson(cover, desc, url), ContentType.Application.Json) - } - post { - //val json = if (isJsonData() && (isJsonObject("json") || isJsonArray("json"))) - // fetchPostJsonElement("json").toString() - //else fetchPostOrThrow("json") - val cover = fetchPostOrThrow("cover") - val desc = fetchPostOrThrow("desc") - val url = fetchPostOrNull("url") ?: "" - call.respondText(AdaptShareJson(cover, desc, url), ContentType.Application.Json) - } - } - - getOrPost("/get_forward_msg") { - val id = fetchOrThrow("id") - call.respondText(GetForwardMsg(id), ContentType.Application.Json) - } - - getOrPost("/get_group_msg_history") { - val peerId = fetchOrThrow("group_id") - val cnt = fetchOrNull("count")?.toInt() ?: 20 - val startId = fetchOrNull("message_id")?.let { - val messageId = it.toInt() - if (messageId == 0) return@let 0L - MessageDB.getInstance() - .messageMappingDao() - .queryByMsgHashId(messageId)?.qqMsgId - } ?: fetchOrNull("message_seq")?.let { - val messageSeq = it.toInt() - if (messageSeq == 0) return@let 0L - MessageDB.getInstance() - .messageMappingDao() - .queryByMsgSeq(MsgConstant.KCHATTYPEGROUP, peerId, messageSeq)?.qqMsgId - } ?: 0L - call.respondText(GetHistoryMsg("group", peerId, cnt, startId), ContentType.Application.Json) - } - - getOrPost("/get_history_msg") { - val msgType = fetchOrThrow("message_type") - val peerId = fetchOrThrow(if (msgType == "group") "group_id" else "user_id") - val cnt = fetchOrNull("count")?.toInt() ?: 20 - val startId = fetchOrNull("message_seq")?.toInt()?.let { - if (it == 0) return@let 0L - MessageDB.getInstance() - .messageMappingDao() - .queryByMsgHashId(it)?.qqMsgId - } ?: 0L - call.respondText(GetHistoryMsg(msgType, peerId, cnt, startId), ContentType.Application.Json) - } - - getOrPost("/clear_msgs") { - val msgType = fetchOrThrow("message_type") - val peerId = fetchOrThrow(if (msgType == "group") "group_id" else "user_id") - call.respondText(ClearMsgs(msgType, peerId), ContentType.Application.Json) - } - - getOrPost("/delete_msg") { - val msgHash = fetchOrThrow("message_id").toInt() - call.respondText(DeleteMessage(msgHash), ContentType.Application.Json) - } - - getOrPost("/get_msg") { - val msgHash = fetchOrThrow("message_id").toInt() - call.respondText(GetMsg(msgHash), ContentType.Application.Json) - } - - getOrPost("/send_msg_by_resid") { - val resId = fetchOrThrow("res_id") - val peerId = fetchOrThrow("peer_Id") - val messageType = fetchOrThrow("message_type") - call.respondText(SendMsgByResid(peerId, resId, messageType)) - } - - route("/(send_msg|send_message)".toRegex()) { - get { - val msgType = fetchGetOrThrow("message_type") - val message = fetchGetOrThrow("message") - val retryCnt = fetchGetOrNull("retry_cnt")?.toInt() ?: 5 - val autoEscape = fetchGetOrNull("auto_escape")?.toBooleanStrict() ?: false - val chatType = MessageHelper.obtainMessageTypeByDetailType(msgType) - - val userId = fetchGetOrNull("user_id") - val groupId = fetchGetOrNull("group_id") - - val recallDuration = fetchGetOrNull("recall_duration")?.toLongOrNull() - - call.respondText( - SendMessage( - chatType = chatType, - peerId = if (chatType == MsgConstant.KCHATTYPEC2C) userId!! else groupId!!, - message = message, - autoEscape = autoEscape, - fromId = groupId ?: userId ?: "", - retryCnt = retryCnt, - recallDuration = recallDuration - ), ContentType.Application.Json - ) - } - post { - val msgType = fetchPostOrThrow("message_type") - val chatType = MessageHelper.obtainMessageTypeByDetailType(msgType) - val retryCnt = fetchPostOrNull("retry_cnt")?.toInt() ?: 3 - - val userId = fetchPostOrNull("user_id") - val groupId = fetchPostOrNull("group_id") - val peerId = if (chatType == MsgConstant.KCHATTYPEC2C) userId!! else groupId!! - val recallDuration = fetchPostOrNull("recall_duration")?.toLongOrNull() - - call.respondText( - if (isJsonData() && !isJsonString("message")) { - SendMessage( - chatType = chatType, - peerId = peerId, - message = if (isJsonObject("message")) listOf(fetchPostJsonObject("message")).jsonArray else fetchPostJsonArray( - "message" - ), - fromId = groupId ?: userId ?: "", - retryCnt = retryCnt, - recallDuration = recallDuration - ) - } else { - val autoEscape = fetchPostOrNull("auto_escape")?.toBooleanStrict() ?: false - //SendMessage(chatType, peerId, fetchPostOrThrow("message"), autoEscape) - SendMessage( - chatType = chatType, - peerId = peerId, - message = fetchPostOrThrow("message"), - autoEscape = autoEscape, - fromId = groupId ?: userId ?: "", - retryCnt = retryCnt, - recallDuration = recallDuration - ) - }, ContentType.Application.Json - ) - } - } - - route("/send_group_(msg|message)".toRegex()) { - get { - val groupId = fetchGetOrThrow("group_id") - val message = fetchGetOrThrow("message") - val retryCnt = fetchGetOrNull("retry_cnt")?.toInt() ?: 3 - val autoEscape = fetchGetOrNull("auto_escape")?.toBooleanStrict() ?: false - val recallDuration = fetchGetOrNull("recall_duration")?.toLongOrNull() - - call.respondText( - SendMessage( - MsgConstant.KCHATTYPEGROUP, - groupId, - message, - autoEscape, - retryCnt = retryCnt, - recallDuration = recallDuration - ), ContentType.Application.Json - ) - } - post { - val groupId = fetchPostOrThrow("group_id") - - val retryCnt = fetchPostOrNull("retry_cnt")?.toInt() ?: 3 - val autoEscape = fetchPostOrNull("auto_escape")?.toBooleanStrict() ?: false - val recallDuration = fetchPostOrNull("recall_duration")?.toLongOrNull() - - val result = if (isJsonData()) { - if (isJsonString("message")) { - SendMessage( - MsgConstant.KCHATTYPEGROUP, - groupId, - fetchPostJsonString("message"), - autoEscape, - retryCnt = retryCnt, - recallDuration = recallDuration - ) - } else { - SendMessage( - chatType = MsgConstant.KCHATTYPEGROUP, - peerId = groupId, - message = if (isJsonObject("message")) listOf(fetchPostJsonObject("message")).jsonArray else fetchPostJsonArray( - "message" - ), - retryCnt = retryCnt, - recallDuration = recallDuration - ) - } - } else { - SendMessage( - MsgConstant.KCHATTYPEGROUP, - groupId, - fetchPostOrThrow("message"), - autoEscape, - retryCnt = retryCnt, - recallDuration = recallDuration - ) - } - - call.respondText(result, ContentType.Application.Json) - } - } - - route("/send_private_(msg|message)".toRegex()) { - get { - val userId = fetchGetOrThrow("user_id") - val groupId = fetchGetOrNull("group_id") - val message = fetchGetOrThrow("message") - val retryCnt = fetchGetOrNull("retry_cnt")?.toInt() ?: 3 - val autoEscape = fetchGetOrNull("auto_escape")?.toBooleanStrict() ?: false - val recallDuration = fetchGetOrNull("recall_duration")?.toLongOrNull() - call.respondText( - SendMessage( - chatType = if (groupId == null) MsgConstant.KCHATTYPEC2C else MsgConstant.KCHATTYPETEMPC2CFROMGROUP, - peerId = userId, - message = message, - autoEscape = autoEscape, - fromId = groupId ?: userId, - retryCnt = retryCnt, recallDuration = recallDuration - ), ContentType.Application.Json - ) - } - post { - val userId = fetchPostOrThrow("user_id") - val groupId = fetchPostOrNull("group_id") - - val chatType = if (groupId == null) MsgConstant.KCHATTYPEC2C else MsgConstant.KCHATTYPETEMPC2CFROMGROUP - - val retryCnt = fetchPostOrNull("retry_cnt")?.toInt() ?: 3 - val autoEscape = fetchPostOrNull("auto_escape")?.toBooleanStrict() ?: false - val recallDuration = fetchPostOrNull("recall_duration")?.toLongOrNull() - - val result = if (isJsonData()) { - if (isJsonString("message")) { - SendMessage( - chatType = chatType, - peerId = userId, - message = fetchPostJsonString("message"), - autoEscape = autoEscape, - fromId = groupId ?: userId, - retryCnt = retryCnt, - recallDuration = recallDuration - ) - } else { - SendMessage( - chatType = chatType, - peerId = userId, - message = if (isJsonObject("message")) listOf(fetchPostJsonObject("message")).jsonArray else fetchPostJsonArray( - "message" - ), - fromId = groupId ?: userId, - retryCnt = retryCnt, - recallDuration = recallDuration - ) - } - } else { - SendMessage( - chatType = chatType, - peerId = userId, - message = fetchPostOrThrow("message"), - autoEscape = autoEscape, - fromId = groupId ?: userId, - retryCnt = retryCnt, - recallDuration = recallDuration - ) - } - - call.respondText(result, ContentType.Application.Json) - } - } - - route("/upload_multi_(msg|message)".toRegex()) { - post { - val msgType = fetchPostOrThrow("message_type") - val chatType = MessageHelper.obtainMessageTypeByDetailType(msgType) - val retryCnt = fetchPostOrNull("retry_cnt")?.toInt() ?: 5 - - val userId = fetchPostOrNull("user_id") - val groupId = fetchPostOrNull("group_id") - val messages = fetchPostJsonArray("messages") - call.respondText(UploadMultiMessage( - chatType = chatType, - peerId = if (chatType == MsgConstant.KCHATTYPEC2C) userId!! else groupId!!, - fromId = groupId ?: userId ?: "", - messages = messages, - retryCnt = retryCnt - ), ContentType.Application.Json) - } - get { - respond(false, Status.InternalHandlerError, "Not support GET method") - } - } - - route("/send_forward_(msg|message)".toRegex()) { - post { - val msgType = fetchPostOrThrow("message_type") - val chatType = MessageHelper.obtainMessageTypeByDetailType(msgType) - val retryCnt = fetchPostOrNull("retry_cnt")?.toInt() ?: 5 - - val userId = fetchPostOrNull("user_id") - val groupId = fetchPostOrNull("group_id") - val messages = fetchPostJsonArray("messages") - call.respondText( - SendForwardMessage( - chatType, - if (chatType == MsgConstant.KCHATTYPEC2C) userId!! else groupId!!, - groupId ?: userId ?: "", - messages, - retryCnt - ), - ContentType.Application.Json - ) - } - get { - respond(false, Status.InternalHandlerError, "Not support GET method") - } - } - - route("/send_private_forward_(msg|message)".toRegex()) { - post { - val userId = fetchPostOrThrow("user_id") - val groupId = fetchPostOrNull("group_id") - - val retryCnt = fetchPostOrNull("retry_cnt")?.toInt() ?: 5 - val messages = fetchPostJsonArray("messages") - call.respondText( - SendForwardMessage(MsgConstant.KCHATTYPEC2C, userId, groupId ?: userId, messages, retryCnt), - ContentType.Application.Json - ) - } - get { - respond(false, Status.InternalHandlerError, "Not support GET method") - } - } - - route("/send_group_forward_(msg|message)".toRegex()) { - post { - val groupId = fetchPostOrThrow("group_id") - - val retryCnt = fetchPostOrNull("retry_cnt")?.toInt() ?: 5 - val messages = fetchPostJsonArray("messages") - call.respondText( - SendForwardMessage(MsgConstant.KCHATTYPEGROUP, groupId, messages = messages, retryCnt = retryCnt), - ContentType.Application.Json - ) - } - get { - respond(false, Status.InternalHandlerError, "Not support GET method") - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/OtherAction.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/OtherAction.kt deleted file mode 100644 index 930426ee..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/OtherAction.kt +++ /dev/null @@ -1,147 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.api - -import io.ktor.http.ContentType -import io.ktor.http.content.PartData -import io.ktor.http.content.forEachPart -import io.ktor.http.content.streamProvider -import io.ktor.server.application.call -import io.ktor.server.request.receiveMultipart -import io.ktor.server.response.respondText -import io.ktor.server.routing.Routing -import io.ktor.server.routing.post -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import kotlinx.coroutines.withTimeoutOrNull -import kotlinx.serialization.json.JsonObject -import moe.fuqiuluo.shamrock.remote.action.handlers.CleanCache -import moe.fuqiuluo.shamrock.remote.action.handlers.DownloadFile -import moe.fuqiuluo.shamrock.remote.action.handlers.GetDeviceBattery -import moe.fuqiuluo.shamrock.remote.action.handlers.GetVersionInfo -import moe.fuqiuluo.shamrock.remote.action.handlers.RestartMe -import moe.fuqiuluo.shamrock.remote.action.handlers.UploadFileToShamrock -import moe.fuqiuluo.shamrock.remote.structures.Status -import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig -import moe.fuqiuluo.shamrock.tools.asString -import moe.fuqiuluo.shamrock.tools.fetchOrNull -import moe.fuqiuluo.shamrock.tools.fetchOrThrow -import moe.fuqiuluo.shamrock.tools.fetchPostJsonArray -import moe.fuqiuluo.shamrock.tools.getOrPost -import moe.fuqiuluo.shamrock.tools.hex2ByteArray -import moe.fuqiuluo.shamrock.tools.isJsonArray -import moe.fuqiuluo.shamrock.tools.json -import moe.fuqiuluo.shamrock.tools.respond -import moe.fuqiuluo.shamrock.utils.FileUtils -import moe.fuqiuluo.shamrock.utils.MD5 -import java.io.File - -fun Routing.otherAction() { - - if (ShamrockConfig.allowShell()) { - post("/shell") { - val runtime = Runtime.getRuntime() - val dir = fetchOrThrow("dir") - val out = hashMapOf() - withTimeoutOrNull(5000L) { - if (isJsonArray("cmd")) { - val cmd = fetchPostJsonArray("cmd").map { - if (it is JsonObject) it.toString() else it.asString - }.toTypedArray() - withContext(Dispatchers.IO) { - runtime.exec(cmd, null, File(dir)).apply { waitFor() } - } - } else { - val cmd = fetchOrThrow("cmd") - withContext(Dispatchers.IO) { - runtime.exec(cmd, null, File(dir)).apply { waitFor() } - } - } - }.also { - if (it == null) { - respond(false, Status.IAmTired, "执行超时") - } else { - it.inputStream.use { - out["out"] = it.readBytes().toString(Charsets.UTF_8) - } - it.errorStream.use { - out["err"] = it.readBytes().toString(Charsets.UTF_8) - } - } - } - - call.respondText(out.json.toString(), ContentType.Application.Json) - } - } - - getOrPost("/get_version_info") { - call.respondText(GetVersionInfo(), ContentType.Application.Json) - } - - getOrPost("/get_device_battery") { - call.respondText(GetDeviceBattery(), ContentType.Application.Json) - } - - getOrPost("/clean_cache") { - call.respondText(CleanCache(), ContentType.Application.Json) - } - - getOrPost("/set_restart") { - call.respondText(RestartMe(2000), ContentType.Application.Json) - } - - getOrPost("/download_file") { - val url = fetchOrNull("url") - val b64 = fetchOrNull("base64") - val name = fetchOrNull("name") - val root = fetchOrNull("root") - val threadCnt = fetchOrNull("thread_cnt")?.toInt() ?: 0 - val headers = fetchOrNull("headers") ?: "" - call.respondText(DownloadFile(url, b64, threadCnt, headers.split("\r\n"), name, root), ContentType.Application.Json) - } - - post("/upload_file") { - val partData = call.receiveMultipart() - partData.forEachPart { part -> - if (part.name == "file") { - val bytes = (part as PartData.FileItem).streamProvider().readBytes() - val tmp = FileUtils.renameByMd5(FileUtils.getTmpFile("cache").also { - it.writeBytes(bytes) - }) - respond(true, Status.Ok, DownloadFile.DownloadResult( - file = tmp.absolutePath, - md5 = MD5.genFileMd5Hex(tmp.absolutePath) - ), "成功") - return@forEachPart - } - } - respond(false, Status.BadRequest, "没有上传文件信息") - } - - getOrPost("/upload_file_to_shamrock") { - val md5 = fetchOrThrow("md5").hex2ByteArray() - val offset = fetchOrNull("offset")?.toULong() ?: 0uL - val chunk = fetchOrThrow("chunk").toByteArray() - val fileSize = fetchOrNull("file_size")?.toULong() ?: chunk.size.toULong() - call.respondText(UploadFileToShamrock(md5, fileSize, offset, chunk), ContentType.Application.Json) - } - - getOrPost("/config/set_boolean") { - val key = fetchOrThrow("key") - val value = fetchOrThrow("value").toBooleanStrict() - ShamrockConfig[key] = value - respond(true, Status.Ok, "success") - } - - getOrPost("/config/set_int") { - val key = fetchOrThrow("key") - val value = fetchOrThrow("value").toInt() - ShamrockConfig[key] = value - respond(true, Status.Ok, "success") - } - - getOrPost("/config/set_string") { - val key = fetchOrThrow("key") - val value = fetchOrThrow("value") - ShamrockConfig[key] = value - respond(true, Status.Ok, "success") - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/ProfileAction.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/ProfileAction.kt deleted file mode 100644 index 0d148e7d..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/ProfileAction.kt +++ /dev/null @@ -1,86 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.api - -import com.tencent.mobileqq.app.QQAppInterface -import io.ktor.http.ContentType -import io.ktor.server.application.call -import io.ktor.server.response.respond -import io.ktor.server.response.respondText -import io.ktor.server.routing.Routing -import moe.fuqiuluo.shamrock.remote.action.ActionManager -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.handlers.GetLoginInfo -import moe.fuqiuluo.shamrock.remote.structures.CommonResult -import moe.fuqiuluo.shamrock.remote.structures.CurrentAccount -import moe.fuqiuluo.shamrock.remote.structures.Status -import moe.fuqiuluo.shamrock.tools.* -import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher -import mqq.app.MobileQQ - -fun Routing.profileRouter() { - getOrPost("/set_qq_profile") { - val nickName = fetchOrThrow("nickname") - val company = fetchOrThrow("company") - val email = fetchOrThrow("email") - val college = fetchOrThrow("college") - val personalNote = fetchOrThrow("personal_note") - - val age = fetchOrNull("age") - val birthday = fetchOrNull("birthday") - - val handler = ActionManager["set_qq_profile"]!! - - call.respondText( - handler.handle( - ActionSession( - mapOf( - "nickname" to nickName, - "company" to company, - "email" to email, - "college" to college, - "personal_note" to personalNote, - "age" to age, - "birthday" to birthday - ) - ) - ), ContentType.Application.Json - ) - } - - getOrPost("/get_account_info") { - val accounts = MobileQQ.getMobileQQ().allAccounts - val runtime = AppRuntimeFetcher.appRuntime - val curUin = runtime.currentAccountUin - val account = accounts?.firstOrNull { it.uin == curUin } - if (!runtime.isLogin || account == null || !account.isLogined) { - this.call.respond( - CommonResult( - "ok", Status.InternalHandlerError.code, CurrentAccount( - 1094950020L, false, "未登录" - ) - ) - ) - } else { - this.call.respond( - CommonResult( - "ok", 0, CurrentAccount( - curUin.toLong(), - runtime.isLogin, - if (runtime is QQAppInterface) runtime.currentNickname else "unknown" - ) - ) - ) - } - } - - getOrPost("/get_history_account_info") { - val accounts = MobileQQ.getMobileQQ().allAccounts - val accountList = accounts.map { - CurrentAccount(it.uin.toLong(), it.isLogined) - } - respond(true, Status.Ok, accountList) - } - - getOrPost("/get_login_info") { - call.respondText(GetLoginInfo(), ContentType.Application.Json) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/ProtocolAction.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/ProtocolAction.kt deleted file mode 100644 index 60e29a9c..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/ProtocolAction.kt +++ /dev/null @@ -1,79 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.api - -import com.tencent.mobileqq.dt.model.FEBound -import io.ktor.server.routing.Routing -import moe.fuqiuluo.qqinterface.servlet.BaseSvc -import moe.fuqiuluo.shamrock.remote.structures.Protocol -import moe.fuqiuluo.shamrock.remote.structures.QSignDtConfig -import moe.fuqiuluo.shamrock.remote.structures.Status -import moe.fuqiuluo.shamrock.tools.* -import moe.fuqiuluo.shamrock.utils.MMKVFetcher -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import mqq.app.MobileQQ -import oicq.wlogin_sdk.tlv_type.tlv_t100 -import oicq.wlogin_sdk.tlv_type.tlv_t106 -import oicq.wlogin_sdk.tlv_type.tlv_t18 -import oicq.wlogin_sdk.tools.util - -fun Routing.obtainProtocolData() { - getOrPost("/send_packet") { - val cmd = fetchOrThrow("cmd") - val isPb = fetchOrThrow("proto").toBooleanStrict() - val buffer = fetchOrThrow("buffer").hex2ByteArray() - val resp = BaseSvc.sendBufferAW(cmd, isPb, buffer) - respond(true, Status.Ok, data = resp?.toHexString() ?: "null", msg = "成功") - } - - getOrPost("/set_guid") { - val guid = fetchOrThrow("guid").hex2ByteArray() - val ctx = MobileQQ.getContext() - util.save_cur_guid(ctx, guid) - util.saveGuidToFile(ctx, guid) - val guildLock = MMKVFetcher.mmkvWithId("guid") - guildLock.putString("guid", guid.toHexString()) - respond(true, Status.Ok, msg = "成功") - } - - getOrPost("/get_msf_info") { - val mqq = MobileQQ.getMobileQQ() - val ctx = MobileQQ.getContext() - - val t18 = tlv_t18() - val t100 = tlv_t100() - val t106 = tlv_t106() - - val qimei = kotlin.runCatching { - util.get_qimei(ctx).toHexString() - }.getOrDefault("") - - val encodeTable = FEBound::class.java.getDeclaredField("mConfigEnCode").also { - it.isAccessible = true - }.get(null) as Array - val decodeTable = FEBound::class.java.getDeclaredField("mConfigDeCode").also { - it.isAccessible = true - }.get(null) as Array - - respond( - isOk = true, - code = Status.Ok, - data = Protocol( - mqq.qqProcessName, - mqq.appId.toLong(), mqq.qua, kotlin.runCatching { mqq.ntCoreVersion }.getOrDefault(0), - mqq.msfConnectedNetType, - qimei, - util.getSvnVersion(), - PlatformUtils.getAndroidID(), - util.getGuidFromFile(ctx).toHexString(), - util.get_ksid(ctx).toHexString(), - util.get_network_type(ctx), - t18._ping_version.toByte(), t18._sso_version, - t100._sso_ver, t100._db_buf_ver, - t106._SSoVer, t106._TGTGTVer, - - util.get_android_dev_info(ctx).toHexString(), - - qSignDtConfig = QSignDtConfig(encodeTable, decodeTable) - ) - ) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/RequestAction.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/RequestAction.kt deleted file mode 100644 index 5a663bbe..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/RequestAction.kt +++ /dev/null @@ -1,39 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.api - -import io.ktor.http.ContentType -import io.ktor.server.application.call -import io.ktor.server.response.respondText -import io.ktor.server.routing.Routing -import moe.fuqiuluo.shamrock.remote.action.handlers.SetFriendAddRequest -import moe.fuqiuluo.shamrock.remote.action.handlers.SetGroupAddRequest -import moe.fuqiuluo.shamrock.tools.fetchOrNull -import moe.fuqiuluo.shamrock.tools.fetchOrThrow -import moe.fuqiuluo.shamrock.tools.getOrPost - -fun Routing.requestRouter() { - getOrPost("/set_friend_add_request") { - val flag = fetchOrThrow("flag") - val approve = fetchOrNull("approve")?.toBooleanStrict() ?: true - val remark = fetchOrNull("remark") - val notSeen = fetchOrNull("not_seen")?.toBooleanStrict() ?: false - - call.respondText( - SetFriendAddRequest(flag, approve, remark, notSeen), - ContentType.Application.Json - ) - } - - getOrPost("/set_group_add_request") { - val flag = fetchOrThrow("flag") - val approve = fetchOrNull("approve")?.toBooleanStrict() ?: true - val remark = fetchOrNull("reason") - val subType = fetchOrThrow("sub_type") - val notSeen = fetchOrNull("not_seen")?.toBooleanStrict() ?: false - - call.respondText( - SetGroupAddRequest(flag, approve, subType, remark, notSeen), - ContentType.Application.Json - ) - } - -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/ResourceAction.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/ResourceAction.kt deleted file mode 100644 index f553edb7..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/ResourceAction.kt +++ /dev/null @@ -1,107 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.api - -import io.ktor.http.ContentType -import moe.fuqiuluo.shamrock.utils.FileUtils -import io.ktor.http.HttpStatusCode -import io.ktor.server.application.call -import io.ktor.server.request.document -import io.ktor.server.response.respond -import io.ktor.server.response.respondFile -import io.ktor.server.response.respondText -import io.ktor.server.routing.Routing -import io.ktor.server.routing.get -import io.ktor.server.routing.route -import moe.fuqiuluo.shamrock.remote.action.handlers.* -import moe.fuqiuluo.shamrock.tools.* - -private fun formatFileName(file: String): String = file - .replace(regex = "[{}\\-]".toRegex(), replacement = "") - .replace(" ", "") - .split(".")[0].lowercase() - -fun Routing.fetchRes() { - getOrPost("/get_record") { - val file = formatFileName( fetchGetOrThrow("file") ) - val format = fetchOrThrow("out_format") - call.respondText(GetRecord(file, format), ContentType.Application.Json) - } - - getOrPost("/get_file") { - val file = formatFileName( fetchGetOrThrow("file") ) - val fileType = fetchOrThrow("file_type") - call.respondText(GetFile(file, fileType), ContentType.Application.Json) - } - - getOrPost("/get_image") { - val file = formatFileName( fetchGetOrThrow("file") ) - call.respondText(GetImage(file), ContentType.Application.Json) - } - - getOrPost("/upload_group_file") { - val groupId = fetchOrThrow("group_id").toLong() - val file = fetchOrThrow("file") - val name = fetchOrThrow("name") - call.respondText(UploadGroupFile(groupId, file, name), ContentType.Application.Json) - } - - getOrPost("/upload_private_file") { - val userId = fetchOrThrow("user_id").toLong() - val file = fetchOrThrow("file") - val name = fetchOrThrow("name") - call.respondText(UploadPrivateFile(userId, file, name), ContentType.Application.Json) - } - - getOrPost("/create_group_file_folder") { - val groupId = fetchOrThrow("group_id").toLong() - val name = fetchOrThrow("name") - call.respondText(CreateGroupFileFolder(groupId, name), ContentType.Application.Json) - } - - getOrPost("/delete_group_folder") { - val groupId = fetchOrThrow("group_id").toLong() - val id = fetchOrThrow("folder_id") - call.respondText(DeleteGroupFolder(groupId, id), ContentType.Application.Json) - } - - getOrPost("/delete_group_file") { - val groupId = fetchOrThrow("group_id").toLong() - val id = fetchOrThrow("file_id") - val busid = fetchOrThrow("busid").toInt() - call.respondText(DeleteGroupFile(groupId, id, busid), ContentType.Application.Json) - } - - getOrPost("/get_group_file_system_info") { - val groupId = fetchOrThrow("group_id").toLong() - call.respondText(GetGroupFileSystemInfo(groupId), ContentType.Application.Json) - } - - getOrPost("/get_group_root_files") { - val groupId = fetchOrThrow("group_id").toLong() - call.respondText(GetGroupRootFiles(groupId), ContentType.Application.Json) - } - - getOrPost("/get_group_files_by_folder") { - val groupId = fetchOrThrow("group_id").toLong() - val folderId = fetchOrThrow("folder_id") - call.respondText(GetGroupSubFiles(groupId, folderId), ContentType.Application.Json) - } - - getOrPost("/get_group_file_url") { - val groupId = fetchOrThrow("group_id").toLong() - val id = fetchOrThrow("file_id") - val busid = fetchOrThrow("busid").toInt() - call.respondText(GetGroupFileUrl(groupId, id, busid), ContentType.Application.Json) - } - - route("/res/[a-fA-F0-9]{32}".toRegex()) { - get { - val md5 = call.request.document() - val file = FileUtils.getFile(md5) - if (!file.exists()) { - call.respond(HttpStatusCode.NotFound) - } else { - call.respondFile(file) - } - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/TestAction.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/TestAction.kt deleted file mode 100644 index 68334d16..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/TestAction.kt +++ /dev/null @@ -1,54 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.api - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import com.tencent.qqnt.kernel.nativeinterface.MsgElement -import com.tencent.qqnt.kernel.nativeinterface.TextElement -import io.ktor.server.application.* -import io.ktor.server.response.* -import io.ktor.server.routing.* -import moe.fuqiuluo.qqinterface.servlet.TicketSvc -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.remote.action.handlers.SendMsgByResid -import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig -import moe.fuqiuluo.shamrock.tools.fetchOrNull -import moe.fuqiuluo.shamrock.tools.fetchOrThrow -import moe.fuqiuluo.shamrock.tools.getOrPost -import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher - -fun Routing.testAction() { - if (ShamrockConfig.isDev()) { - LogCenter.log("testAction is enabled.", Level.WARN) - } else { - return - } - - getOrPost("/createUidFromTinyId") { - val selfId = fetchOrThrow("selfId").toLong() - val peerId = fetchOrThrow("peerId") - call.respondText( - NTServiceFetcher.kernelService.wrapperSession.msgService.createUidFromTinyId( - selfId, - peerId.toLong() - ) - ) - } - - getOrPost("/addSendMsg") { - val msgService = NTServiceFetcher.kernelService.wrapperSession.msgService - val msgId = msgService.getMsgUniqueId(System.currentTimeMillis()) - msgService.addSendMsg(msgId, - MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, TicketSvc.getUin()), - arrayListOf( - MsgElement().apply { - elementType = MsgConstant.KELEMTYPETEXT - textElement = TextElement().apply { - content = "测试消息" - } - } - ), - hashMapOf()) - call.respondText("ok") - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/TicketAction.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/TicketAction.kt deleted file mode 100644 index f082d0f4..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/TicketAction.kt +++ /dev/null @@ -1,81 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.api - -import io.ktor.http.ContentType -import moe.fuqiuluo.qqinterface.servlet.TicketSvc -import io.ktor.server.application.call -import io.ktor.server.response.respondText -import io.ktor.server.routing.Routing -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.shamrock.remote.action.handlers.* -import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig -import moe.fuqiuluo.shamrock.remote.structures.Status -import moe.fuqiuluo.shamrock.tools.* - -fun Routing.ticketActions() { - getOrPost("/get_http_cookies") { - val appid =fetchOrThrow("appid") - val daid = fetchOrThrow("daid") - val jumpurl = fetchOrThrow("jumpurl") - call.respondText(GetHttpCookies(appid, daid, jumpurl), ContentType.Application.Json) - } - - getOrPost("/get_credentials") { - val domain = fetchOrNull("domain") - if (domain != null) { - call.respondText(GetCredentials(domain), ContentType.Application.Json) - } else { - call.respondText(GetCredentials(), ContentType.Application.Json) - } - } - - getOrPost("/get_cookies") { - val domain = fetchOrNull("domain") - if (domain != null) { - call.respondText(GetCookies(domain = domain), ContentType.Application.Json) - } else { - call.respondText(GetCookies(), ContentType.Application.Json) - } - } - - getOrPost("/get_csrf_token") { - val domain = fetchOrNull("domain") - if (domain != null) { - call.respondText(GetCSRF(domain), ContentType.Application.Json) - } else { - call.respondText(GetCSRF(), ContentType.Application.Json) - } - } - - fun getTicket(uin: String, id: Int, debug: Boolean = false) = TicketSvc.getTicket(uin, id)?.let { - mutableMapOf( - "sig" to (it._sig?.toHexString() ?: "null"), - "key" to (it._sig_key?.toHexString() ?: "null") - ).also { map -> - if (debug) - map["content"] = ((it._sig?.decodeToString() ?: "") + ":" + (it._sig_key?.decodeToString() ?: "null")) - }.json.asJsonObject - } ?: EmptyJsonObject - - getOrPost("/get_ticket") { - val uin = fetchOrThrow("uin") - val ticket = when(val id = fetchOrThrow("id").toInt()) { - 32 -> TicketSvc.getStWeb(uin) - else -> { - respond(true, Status.Ok, data = getTicket(uin, id)) - return@getOrPost - } - } - respond(true, Status.Ok, data = ticket) - } - - if (ShamrockConfig.isDev()) getOrPost("/get_all_ticket") { - val uin = fetchOrThrow("uin") - - val ticketMap = mutableMapOf() - TicketSvc.SigType.ALL_TICKET.forEach { - ticketMap[it] = getTicket(uin, it, true) - } - - respond(true, Status.Ok, data = ticketMap) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/UserAction.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/UserAction.kt deleted file mode 100644 index bcd1847d..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/UserAction.kt +++ /dev/null @@ -1,56 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.api - -import io.ktor.http.ContentType -import moe.fuqiuluo.shamrock.helper.LogicException -import io.ktor.server.application.call -import io.ktor.server.response.respondText -import io.ktor.server.routing.Routing -import moe.fuqiuluo.shamrock.remote.action.ActionManager -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.handlers.* -import moe.fuqiuluo.shamrock.tools.* -import moe.fuqiuluo.shamrock.utils.PlatformUtils - -fun Routing.userAction() { - getOrPost("/switch_account") { - val userId = fetchOrThrow("user_id").toLong() - call.respondText(SwitchAccount(userId), ContentType.Application.Json) - } - - getOrPost("/set_group_leave") { - val groupId = fetchOrThrow("group_id").toLong() - call.respondText(LeaveTroop(groupId), ContentType.Application.Json) - } - - getOrPost("/_get_online_clients") { - call.respondText(GetOnlineClients(), ContentType.Application.Json) - } - - getOrPost("/_get_model_show") { - val model = fetchOrThrow("model") - call.respondText(GetModelShowList(model), ContentType.Application.Json) - } - - getOrPost("/_set_model_show") { - val model = fetchOrThrow("model") - val manu = fetchOrNull("manu") ?: fetchOrThrow("model_show") - val modelshow = fetchOrNull("modelshow") ?: "Android" - val imei = fetchOrNull("imei") ?: PlatformUtils.getAndroidID() - val show = fetchOrNull("show")?.toBooleanStrictOrNull() ?: true - call.respondText(SetModelShow(model, manu, modelshow, imei, show), ContentType.Application.Json) - } - - getOrPost("/get_model_show") { - val uin = fetchOrNull("user_id") - call.respondText(GetModelShow(uin?.toLong() ?: 0), ContentType.Application.Json) - } - - getOrPost("/send_like") { - val uin = fetchOrThrow("user_id") - val cnt = fetchOrThrow("times") - call.respondText(SendLike( - uin.toLong(), - cnt.toInt() - ), ContentType.Application.Json) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/WeatherAction.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/WeatherAction.kt deleted file mode 100644 index d52a296b..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/WeatherAction.kt +++ /dev/null @@ -1,26 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.api - -import io.ktor.http.ContentType -import io.ktor.server.application.call -import io.ktor.server.response.respondText -import io.ktor.server.routing.Routing -import moe.fuqiuluo.shamrock.remote.action.handlers.GetWeather -import moe.fuqiuluo.shamrock.remote.action.handlers.GetWeatherCityCode -import moe.fuqiuluo.shamrock.tools.* - -fun Routing.weatherAction() { - getOrPost("/get_weather") { - val cityCode = fetchOrNull("code") - if (cityCode == null) { - val city = fetchOrThrow("city") - call.respondText(GetWeather(city)) - return@getOrPost - } - call.respondText(GetWeather(cityCode.toInt()), ContentType.Application.Json) - } - - getOrPost("/get_weather_city_code") { - val city = fetchOrThrow("city") - call.respondText(GetWeatherCityCode(city), ContentType.Application.Json) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/config/ContentNegotiation.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/config/ContentNegotiation.kt deleted file mode 100644 index 4636c349..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/config/ContentNegotiation.kt +++ /dev/null @@ -1,17 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.config - -import io.ktor.serialization.kotlinx.json.json -import io.ktor.server.application.Application -import io.ktor.server.application.install -import io.ktor.server.plugins.contentnegotiation.ContentNegotiation -import kotlinx.serialization.json.Json - -fun Application.contentNegotiation() { - install(ContentNegotiation) { - json(Json { - prettyPrint = true - isLenient = true - ignoreUnknownKeys = true - }) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/config/StatusPages.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/config/StatusPages.kt deleted file mode 100644 index 18b0312c..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/config/StatusPages.kt +++ /dev/null @@ -1,77 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.config - -import moe.fuqiuluo.shamrock.helper.ErrorTokenException -import moe.fuqiuluo.shamrock.helper.LogicException -import moe.fuqiuluo.shamrock.helper.ParamsException -import io.ktor.server.application.Application -import io.ktor.server.application.install -import io.ktor.server.plugins.statuspages.StatusPages -import io.ktor.server.request.uri -import io.ktor.server.response.respond -import io.ktor.util.AttributeKey -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.remote.structures.CommonResult -import moe.fuqiuluo.shamrock.remote.structures.ErrorCatch -import moe.fuqiuluo.shamrock.remote.structures.Status - -val ECHO_KEY = AttributeKey("echo") - -fun Application.statusPages() { - install(StatusPages) { - exception { call, cause -> - val echo = if (call.attributes.contains(ECHO_KEY)) { - call.attributes[ECHO_KEY] - } else null - call.respond( - CommonResult( - status = "failed", - retcode = Status.BadParam.code, - data = ErrorCatch(call.request.uri, cause.message ?: ""), - echo = echo - ) - ) - } - exception { call, cause -> - val echo = if (call.attributes.contains(ECHO_KEY)) { - call.attributes[ECHO_KEY] - } else null - call.respond( - CommonResult( - status = "failed", - retcode = Status.LogicError.code, - data = ErrorCatch(call.request.uri, cause.message ?: ""), - echo = echo - ) - ) - } - exception { call, cause -> - val echo = if (call.attributes.contains(ECHO_KEY)) { - call.attributes[ECHO_KEY] - } else null - call.respond( - CommonResult( - status = "failed", - retcode = Status.ErrorToken.code, - data = ErrorCatch(call.request.uri, cause.message ?: ""), - echo = echo - ) - ) - } - exception { call, cause -> - val echo = if (call.attributes.contains(ECHO_KEY)) { - call.attributes[ECHO_KEY] - } else null - LogCenter.log(cause.stackTraceToString(), Level.ERROR) - call.respond( - CommonResult( - status = "failed", - retcode = Status.InternalHandlerError.code, - data = ErrorCatch(call.request.uri, cause.message ?: ""), - echo = echo - ) - ) - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/plugin/Auth.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/plugin/Auth.kt deleted file mode 100644 index 8f1149a5..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/plugin/Auth.kt +++ /dev/null @@ -1,53 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.plugin - -import io.ktor.server.application.ApplicationCall -import moe.fuqiuluo.shamrock.helper.ErrorTokenException -import io.ktor.server.application.createApplicationPlugin -import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig -import moe.fuqiuluo.shamrock.tools.fetchOrNull -import java.net.URLDecoder -import java.nio.charset.Charset - -private suspend fun ApplicationCall.checkToken() { - val token = ShamrockConfig.getToken() - if (token.isBlank()) { - return - } - var accessToken = request.headers["Authorization"] - ?: fetchOrNull("ticket")?.let { - URLDecoder.decode(it) - } - ?: fetchOrNull("access_token")?.let { - URLDecoder.decode(it) - } - ?: fetchOrNull("token")?.let { - URLDecoder.decode(it) - } - ?: throw ErrorTokenException - if (accessToken.startsWith("Bearer ", ignoreCase = true)) { - accessToken = accessToken.substring(7) - } - if (token != accessToken) { - throw ErrorTokenException - } -} - -internal val Auth = createApplicationPlugin("Auth") { - // 获取get请求的token参数并校验 - this.onCall { call -> - call.checkToken() - } - /* - this.onCallReceive { call, _ -> - var accessToken = call.fetchOrNull("access_token") - ?: call.fetchOrNull("ticket") - ?: call.request.headers["Authorization"] - ?: throw ErrorTokenException - if (accessToken.startsWith("Bearer ")) { - accessToken = accessToken.substring(7) - } - if (token != accessToken) { - throw ErrorTokenException - } - }*/ -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/HttpService.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/HttpService.kt deleted file mode 100644 index ae6407c3..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/HttpService.kt +++ /dev/null @@ -1,144 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) -package moe.fuqiuluo.shamrock.remote.service - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import moe.fuqiuluo.shamrock.helper.MessageHelper -import com.tencent.qqnt.kernel.nativeinterface.MsgRecord -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.qqinterface.servlet.MsgSvc -import io.ktor.client.statement.bodyAsText -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import moe.fuqiuluo.shamrock.remote.service.api.HttpTransmitServlet -import moe.fuqiuluo.shamrock.tools.* -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.remote.action.ActionManager -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.handlers.QuickOperation.quicklyReply -import moe.fuqiuluo.shamrock.remote.service.api.GlobalEventTransmitter - -internal object HttpService: HttpTransmitServlet() { - private val subscribes = arrayListOf() - - override fun subscribe(job: Job) { - subscribes.add(job) - } - - override fun unsubscribe() { - subscribes.removeIf { - it.cancel() - return@removeIf true - } - } - - override fun init() { - if (subscribes.isNotEmpty()) return - subscribe(GlobalScope.launch { - GlobalEventTransmitter.onMessageEvent { (record, event) -> - val respond = pushTo(event) ?: return@onMessageEvent - handleQuicklyReply(record, event.messageId, respond.bodyAsText()) - } - }) - subscribe(GlobalScope.launch { - GlobalEventTransmitter.onNoticeEvent { event -> - pushTo(event) - } - }) - subscribe(GlobalScope.launch { - GlobalEventTransmitter.onRequestEvent { - pushTo(it) - } - }) - LogCenter.log("HttpService: 初始化服务", Level.WARN) - } - - private suspend fun handleQuicklyReply(record: MsgRecord, msgHash: Int, jsonText: String) { - try { - val data = Json.parseToJsonElement(jsonText) - - if (data is JsonObject) { - if (data.containsKey("reply")) { - LogCenter.log({ "quickly reply successfully" }, Level.DEBUG) - val autoEscape = data["auto_escape"].asBooleanOrNull ?: false - val atSender = data["at_sender"].asBooleanOrNull ?: false - val autoReply = data["auto_reply"].asBooleanOrNull ?: true - val message = data["reply"] - if (message is JsonPrimitive) { - if (autoEscape) { - val msgList = mutableSetOf() - msgList.add(mapOf( - "type" to "text", - "data" to mapOf( - "text" to message.asString - ) - ).json) - quicklyReply( - record, - msgList.jsonArray, - msgHash, - atSender, - autoReply - ) - } else { - val messageArray = MessageHelper.decodeCQCode(message.asString) - quicklyReply( - record, - messageArray, - msgHash, - atSender, - autoReply - ) - } - } else if (message is JsonArray) { - quicklyReply( - record, - message, - msgHash, - atSender, - autoReply - ) - } - } - if (MsgConstant.KCHATTYPEGROUP == record.chatType && data.containsKey("delete") && data["delete"].asBoolean) { - MsgSvc.recallMsg(msgHash) - } - if (MsgConstant.KCHATTYPEGROUP == record.chatType && data.containsKey("kick") && data["kick"].asBoolean) { - GroupSvc.kickMember(record.peerUin, false, "", record.senderUin) - } - if (MsgConstant.KCHATTYPEGROUP == record.chatType && data.containsKey("ban") && data["ban"].asBoolean) { - val banTime = data["ban_duration"].asIntOrNull ?: (30 * 60) - if (banTime <= 0) return - GroupSvc.banMember(record.peerUin, record.senderUin, banTime) - } - } else if (data is JsonArray) { - data.forEach { - handleQuicklyActions(it.asJsonObject) - } - } - } catch (e: Throwable) { - LogCenter.log("处理快速操作错误: $e", Level.WARN) - } - } - - private suspend fun handleQuicklyActions(data: JsonObject) { - val action = data["action"].asString - val echo = data["echo"] ?: EmptyJsonString - - val params = data["params"].asJsonObjectOrNull ?: EmptyJsonObject - - val handler = ActionManager[action] - if (handler == null) { - LogCenter.log("HTTP快速操作:不支持的Action: $action", Level.WARN) - } else { - handler.handle(ActionSession(params, echo)) - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/PacketReceiver.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/PacketReceiver.kt deleted file mode 100644 index 6d2eedd2..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/PacketReceiver.kt +++ /dev/null @@ -1,69 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) - -package moe.fuqiuluo.shamrock.remote.service - -import com.tencent.qphone.base.remote.FromServiceMsg -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import moe.fuqiuluo.shamrock.tools.broadcast -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.xposed.helper.internal.DynamicReceiver -import moe.fuqiuluo.shamrock.xposed.helper.internal.IPCRequest -import mqq.app.MobileQQ - -internal object PacketReceiver { - private val allowCommandList: MutableSet by lazy { mutableSetOf( - "trpc.msg.olpush.OlPushService.MsgPush", - - ) } // 非动态注册,永久常驻的包 - private val HandlerByIpcSet = hashSetOf() - - fun init() { - DynamicReceiver.register("register_handler_cmd", IPCRequest { - val cmd = it.getStringExtra("handler_cmd")!! - LogCenter.log({ "RegisterHandler(cmd = $cmd)" }, Level.DEBUG) - HandlerByIpcSet.add(cmd) - }) - DynamicReceiver.register("unregister_handler_cmd", IPCRequest { - val cmd = it.getStringExtra("handler_cmd")!! - LogCenter.log({ "UnRegisterHandler(cmd = $cmd)" }, Level.DEBUG) - HandlerByIpcSet.remove(cmd) - }) - MobileQQ.getContext().broadcast("xqbot") { - putExtra("__cmd", "msf_waiter") - LogCenter.log("MSF Packet Receiver running!") - } - } - - private fun onReceive(from: FromServiceMsg) { - if (HandlerByIpcSet.contains(from.serviceCmd) - || allowCommandList.contains(from.serviceCmd) - ) { - LogCenter.log({ "ReceivePacket(cmd = ${from.serviceCmd})" }, Level.DEBUG) - MobileQQ.getContext().broadcast("xqbot") { - putExtra("__cmd", from.serviceCmd) - putExtra("buffer", from.wupBuffer) - putExtra("seq", from.requestSsoSeq) - } - } else { - val seq = if (from.appSeq == -1) from.requestSsoSeq else from.appSeq - val hash = (from.serviceCmd + seq).hashCode() - LogCenter.log({ "ReceivePacket[$hash](cmd = ${from.serviceCmd}, seq = $seq)" }, Level.DEBUG) - MobileQQ.getContext().broadcast("xqbot") { - putExtra("__hash", hash) - putExtra("buffer", from.wupBuffer) - putExtra("seq", seq) - } - } - } - - fun internalOnReceive(from: FromServiceMsg?) { - if (from == null) return - GlobalScope.launch(Dispatchers.Default) { - onReceive(from) - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/WebSocketClientService.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/WebSocketClientService.kt deleted file mode 100644 index 26f1f7c9..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/WebSocketClientService.kt +++ /dev/null @@ -1,54 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.service - -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import moe.fuqiuluo.shamrock.remote.service.api.WebSocketClientServlet -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.remote.service.api.GlobalEventTransmitter.onMessageEvent -import moe.fuqiuluo.shamrock.remote.service.api.GlobalEventTransmitter.onNoticeEvent -import moe.fuqiuluo.shamrock.remote.service.api.GlobalEventTransmitter.onRequestEvent - -internal class WebSocketClientService( - override val address: String, - heartbeatInterval: Long, - wsHeaders: Map -) : WebSocketClientServlet(address, heartbeatInterval, wsHeaders) { - private val subscribes = mutableSetOf() - - init { - startHeartbeatTimer() - } - - override fun subscribe(job: Job) { - subscribes.add(job) - } - - override fun init() { - subscribe(launch { - onMessageEvent { (_, event) -> - pushTo(event) - } - }) - subscribe(launch { - onNoticeEvent { event -> - pushTo(event) - } - }) - subscribe(launch { - onRequestEvent { event -> - pushTo(event) - } - }) - LogCenter.log("WebSocketClientService: 初始化服务", Level.WARN) - } - - override fun unsubscribe() { - subscribes.removeIf { job -> - job.cancel() - return@removeIf true - } - LogCenter.log("WebSocketClientService: 释放服务", Level.WARN) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/WebSocketService.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/WebSocketService.kt deleted file mode 100644 index b6cd7347..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/WebSocketService.kt +++ /dev/null @@ -1,99 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) - -package moe.fuqiuluo.shamrock.remote.service - -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import moe.fuqiuluo.shamrock.helper.ErrorTokenException -import moe.fuqiuluo.shamrock.remote.service.api.WebSocketTransmitServlet -import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig -import moe.fuqiuluo.shamrock.remote.service.data.BotStatus -import moe.fuqiuluo.shamrock.remote.service.data.Self -import moe.fuqiuluo.shamrock.remote.service.data.push.* -import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.remote.service.api.GlobalEventTransmitter.onMessageEvent -import moe.fuqiuluo.shamrock.remote.service.api.GlobalEventTransmitter.onNoticeEvent -import moe.fuqiuluo.shamrock.remote.service.api.GlobalEventTransmitter.onRequestEvent -import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher -import org.java_websocket.WebSocket -import org.java_websocket.handshake.ClientHandshake -import java.net.URI - -internal class WebSocketService( - host: String, - port: Int, - heartbeatInterval: Long, -): WebSocketTransmitServlet(host, port, heartbeatInterval) { - private val subscribes = mutableSetOf() - - override fun subscribe(job: Job) { - subscribes.add(job) - } - - override fun init() { - subscribe(launch { - onMessageEvent { (_, event) -> pushTo(event) } - }) - subscribe(launch { - onNoticeEvent { event -> pushTo(event) } - }) - subscribe(launch { - onRequestEvent { event -> pushTo(event) } - }) - LogCenter.log("WebSocketService: 初始化服务", Level.WARN) - } - - override fun unsubscribe() { - subscribes.removeIf { job -> - job.cancel() - return@removeIf true - } - LogCenter.log("WebSocketService: 释放服务", Level.WARN) - } - - override fun onOpen(conn: WebSocket, handshake: ClientHandshake) { - val token = ShamrockConfig.getActiveWebSocketConfig()?.tokens - ?: ShamrockConfig.getActiveWebSocketConfig()?.token?.split(",", "|", ",") - ?: listOf(ShamrockConfig.getToken()) - if (token.isNotEmpty()) { - var accessToken = handshake.getFieldValue("access_token") - .ifNullOrEmpty(handshake.getFieldValue("ticket")) - .ifNullOrEmpty(handshake.getFieldValue("Authorization")) - ?: throw ErrorTokenException - if (accessToken.startsWith("Bearer ", ignoreCase = true)) { - accessToken = accessToken.substring(7) - } - if (!token.contains(accessToken)) { - conn.close() - LogCenter.log({ "WSServer连接错误(${conn.remoteSocketAddress.address.hostAddress}:${conn.remoteSocketAddress.port}) 没有提供正确的token, $accessToken。" }, Level.ERROR) - return - } - } - val path = URI.create(handshake.resourceDescriptor).path - if (path != "/api") { - eventReceivers.add(conn) - pushMetaLifecycle() - } - LogCenter.log({ "WSServer连接(${conn.remoteSocketAddress.address.hostAddress}:${conn.remoteSocketAddress.port}$path)" }, Level.WARN) - } - - private fun pushMetaLifecycle() { - launch { - val runtime = AppRuntimeFetcher.appRuntime - pushTo(PushMetaEvent( - time = System.currentTimeMillis() / 1000, - selfId = app.longAccountUin, - postType = PostType.Meta, - type = MetaEventType.LifeCycle, - subType = MetaSubType.Connect, - status = BotStatus( - Self("qq", runtime.longAccountUin), runtime.isLogin, status = "正常", good = true - ), - interval = heartbeatInterval - )) - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/api/BaseTransmitServlet.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/api/BaseTransmitServlet.kt deleted file mode 100644 index a25e4eb1..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/api/BaseTransmitServlet.kt +++ /dev/null @@ -1,24 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.service.api - -import com.tencent.mobileqq.app.QQAppInterface -import kotlinx.coroutines.Job -import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher -import oicq.wlogin_sdk.tools.MD5 - -internal interface BaseTransmitServlet { - val address: String - - fun transmitAccess(): Boolean - - fun subscribe(job: Job) - - fun unsubscribe() - - fun init() - - val app: QQAppInterface - get() = AppRuntimeFetcher.appRuntime as QQAppInterface - - val id: String - get() = MD5.getMD5String(address.toByteArray()) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/api/GlobalEventTransmitter.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/api/GlobalEventTransmitter.kt deleted file mode 100644 index 2f6d1e73..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/api/GlobalEventTransmitter.kt +++ /dev/null @@ -1,606 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) - -package moe.fuqiuluo.shamrock.remote.service.api - -import com.tencent.qqnt.kernel.nativeinterface.MsgElement -import com.tencent.qqnt.kernel.nativeinterface.MsgRecord -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.flow.FlowCollector -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.launch -import moe.fuqiuluo.qqinterface.servlet.BaseSvc -import moe.fuqiuluo.qqinterface.servlet.CardSvc -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.qqinterface.servlet.msg.toSegments -import moe.fuqiuluo.qqinterface.servlet.msg.toJson -import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig -import moe.fuqiuluo.shamrock.remote.service.data.push.GroupFileMsg -import moe.fuqiuluo.shamrock.remote.service.data.push.MemberRole -import moe.fuqiuluo.shamrock.remote.service.data.push.MsgSubType -import moe.fuqiuluo.shamrock.remote.service.data.push.MsgType -import moe.fuqiuluo.shamrock.remote.service.data.push.PostType -import moe.fuqiuluo.shamrock.remote.service.data.push.MessageEvent -import moe.fuqiuluo.shamrock.remote.service.data.push.MessageSource -import moe.fuqiuluo.shamrock.remote.service.data.push.MessageTempSource -import moe.fuqiuluo.shamrock.remote.service.data.push.NoticeEvent -import moe.fuqiuluo.shamrock.remote.service.data.push.NoticeSubType -import moe.fuqiuluo.shamrock.remote.service.data.push.NoticeType -import moe.fuqiuluo.shamrock.remote.service.data.push.PokeDetail -import moe.fuqiuluo.shamrock.remote.service.data.push.PrivateFileMsg -import moe.fuqiuluo.shamrock.remote.service.data.push.RequestEvent -import moe.fuqiuluo.shamrock.remote.service.data.push.RequestSubType -import moe.fuqiuluo.shamrock.remote.service.data.push.RequestType -import moe.fuqiuluo.shamrock.remote.service.data.push.Sender -import moe.fuqiuluo.shamrock.remote.service.data.push.SignDetail -import moe.fuqiuluo.shamrock.tools.ShamrockDsl -import moe.fuqiuluo.shamrock.tools.json -import java.util.ArrayList - -internal object GlobalEventTransmitter: BaseSvc() { - private val messageEventFlow by lazy { - MutableSharedFlow>() - } - private val noticeEventFlow by lazy { - MutableSharedFlow() - } - private val requestEventFlow by lazy { - MutableSharedFlow() - } - - private suspend fun pushNotice(noticeEvent: NoticeEvent) = noticeEventFlow.emit(noticeEvent) - - private suspend fun pushRequest(requestEvent: RequestEvent) = requestEventFlow.emit(requestEvent) - - private suspend fun transMessageEvent(record: MsgRecord, message: MessageEvent) = messageEventFlow.emit(record to message) - - /** - * 消息 - */ - object MessageTransmitter { - /** - * 推送群聊消息 - */ - suspend fun transGroupMessage( - record: MsgRecord, - elements: ArrayList, - rawMsg: String, - msgHash: Int, - postType: PostType - ): Boolean { - val uin = app.longAccountUin - transMessageEvent(record, - MessageEvent( - time = record.msgTime, - selfId = uin, - postType = postType, - messageType = MsgType.Group, - subType = MsgSubType.NORMAL, - messageId = msgHash, - groupId = record.peerUin, - peerId = uin, - userId = record.senderUin, - message = if(ShamrockConfig.useCQ()) rawMsg.json - else elements.toSegments(record.chatType, record.peerUin.toString(), "0").toJson(), - rawMessage = rawMsg, - font = 0, - sender = Sender( - userId = record.senderUin, - nickname = record.sendNickName - .ifEmpty { record.sendRemarkName } - .ifEmpty { record.sendMemberName } - .ifEmpty { record.peerName }, - card = record.sendMemberName, - role = GroupSvc.getMemberRole(record.peerUin, record.senderUin)/*when (record.senderUin) { - GroupSvc.getOwner(record.peerUin.toString()) -> MemberRole.Owner - in GroupSvc.getAdminList(record.peerUin.toString()) -> MemberRole.Admin - else -> MemberRole.Member - }*/, - title = "", - level = "", - ) - ) - ) - return true - } - - /** - * 推送私聊消息 - */ - suspend fun transPrivateMessage( - record: MsgRecord, - elements: ArrayList, - rawMsg: String, - msgHash: Int, - postType: PostType, - tempSource: MessageTempSource = MessageTempSource.Unknown, - groupId: Long = Long.MIN_VALUE, - fromNick: String? = null - ): Boolean { - val botUin = app.longAccountUin - var nickName = record.sendNickName - if (nickName.isNullOrEmpty()) { - CardSvc.getProfileCard(record.senderUin).onSuccess { - nickName = it.strNick ?: record.peerName - } - } - transMessageEvent(record, - MessageEvent( - time = record.msgTime, - selfId = botUin, - postType = postType, - messageType = MsgType.Private, - subType = MsgSubType.Friend, - messageId = msgHash, - targetId = record.peerUin, - peerId = botUin, - userId = record.senderUin, - message = if(ShamrockConfig.useCQ()) rawMsg.json - else elements.toSegments(record.chatType, record.peerUin.toString(), "0").toJson(), - rawMessage = rawMsg, - font = 0, - sender = Sender( - userId = record.senderUin, - nickname = nickName, - card = record.sendMemberName, - role = MemberRole.Member, - title = "", - level = "", - ), - tmpSource = tempSource.id, - groupId = groupId, - fromNickName = fromNick - ) - ) - return true - } - - /** - * 推送私聊消息 - */ - suspend fun transGuildMessage( - record: MsgRecord, - elements: ArrayList, - rawMsg: String, - msgHash: Int, - postType: PostType, - ): Boolean { - val botUin = app.longAccountUin - var nickName = record.sendNickName - if (nickName.isNullOrEmpty()) { - CardSvc.getProfileCard(record.senderUin).onSuccess { - nickName = it.strNick ?: record.peerName - } - } - transMessageEvent(record, - MessageEvent( - time = record.msgTime, - selfId = botUin, - postType = postType, - messageType = MsgType.Guild, - subType = MsgSubType.Channel, - guildId = record.guildId, - channelId = record.channelId, - messageId = msgHash, - targetId = record.peerUin, - peerId = botUin, - userId = record.senderUin, - message = if(ShamrockConfig.useCQ()) rawMsg.json - else elements.toSegments(record.chatType, record.guildId, record.channelId).toJson(), - rawMessage = rawMsg, - font = 0, - sender = Sender( - userId = record.senderUin, - nickname = nickName, - card = record.sendMemberName, - role = MemberRole.Member, // TODO(GUILD ROLE) - title = record.sendNickName, - level = record.roleId.toString(), - tinyId = record.senderUid - ), - ) - ) - return true - } - } - - /** - * 文件通知 通知器 - */ - object FileNoticeTransmitter { - /** - * 推送私聊文件事件 - */ - suspend fun transPrivateFileEvent( - msgTime: Long, - userId: Long, - fileId: String, - fileSubId: String, - fileName: String, - fileSize: Long, - 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 - ), - messageSource =MessageSource.Private - )) - return true - } - - /** - * 推送私聊文件事件 - */ - suspend fun transGroupFileEvent( - msgTime: Long, - userId: Long, - groupId: Long, - uuid: String, - fileName: String, - fileSize: Long, - bizId: Int, - url: String - ): Boolean { - pushNotice(NoticeEvent( - time = msgTime, - selfId = app.longAccountUin, - postType = PostType.Notice, - type = NoticeType.GroupUpload, - groupId = groupId, - operatorId = userId, - userId = userId, - file = GroupFileMsg( - id = uuid, - name = fileName, - size = fileSize, - busid = bizId.toLong(), - url = url - ), - messageSource =MessageSource.Private - )) - return true - } - } - - /** - * 群聊通知 通知器 - */ - 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, - groupId = groupCode, - userId = target, - target = target, - signDetail = SignDetail( - rankImg = rankImg, - action = action - ), - messageSource =MessageSource.Group - )) - 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, - messageSource =MessageSource.Group, - pokeDetail = PokeDetail( - action = action, - suffix = suffix, - actionImg = actionImg - ) - )) - return true - } - - suspend fun transGroupMemberNumChanged( - time: Long, - target: Long, - targetUid: String, - groupCode: Long, - operator: Long, - operatorUid: String, - noticeType: NoticeType, - noticeSubType: NoticeSubType - ): Boolean { - pushNotice(NoticeEvent( - time = time, - selfId = app.longAccountUin, - postType = PostType.Notice, - type = noticeType, - subType = noticeSubType, - groupId = groupCode, - operatorId = operator, - operatorUid = operatorUid, - userId = target, - userUid = targetUid, - senderId = operator, - target = target, - targetUid = targetUid, - messageSource =MessageSource.Group, - )) - return true - } - - suspend fun transGroupAdminChanged( - msgTime: Long, - target: Long, - targetUid: String, - 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, - groupId = groupCode, - operatorId = 0, - userId = target, - userUid = targetUid, - target = target, - targetUid = targetUid, - messageSource =MessageSource.Group, - )) - return true - } - - suspend fun transGroupBan( - msgTime: Long, - subType: NoticeSubType, - operator: Long, - operatorUid: String, - target: Long, - targetUid: String, - groupCode: Long, - duration: Int - ): Boolean { - pushNotice(NoticeEvent( - time = msgTime, - selfId = app.longAccountUin, - postType = PostType.Notice, - type = NoticeType.GroupBan, - subType = subType, - groupId = groupCode, - operatorId = operator, - operatorUid = operatorUid, - userId = target, - senderId = operator, - duration = duration, - target = target, - targetUid = targetUid, - messageSource =MessageSource.Group, - )) - return true - } - - suspend fun transGroupMsgRecall( - time: Long, - operator: Long, - target: Long, - groupCode: Long, - msgHash: Int, - tipText: String - ): Boolean { - pushNotice(NoticeEvent( - time = time, - selfId = app.longAccountUin, - postType = PostType.Notice, - type = NoticeType.GroupRecall, - groupId = groupCode, - operatorId = operator, - userId = target, - msgId = msgHash, - tip = tipText, - messageSource =MessageSource.Group, - )) - return true - } - - suspend fun transCardChange( - time: Long, - targetId: Long, - oldCard: String, - newCard: String, - groupId: Long - ): Boolean { - pushNotice(NoticeEvent( - time = time, - selfId = app.longAccountUin, - postType = PostType.Notice, - type = NoticeType.GroupCard, - groupId = groupId, - userId = targetId, - cardNew = newCard, - cardOld = oldCard, - messageSource =MessageSource.Group, - )) - return true - } - - suspend fun transTitleChange( - time: Long, - targetId: Long, - title: String, - groupId: Long - ): Boolean { - pushNotice(NoticeEvent( - time = time, - selfId = app.longAccountUin, - postType = PostType.Notice, - type = NoticeType.Notify, - subType = NoticeSubType.Title, - groupId = groupId, - userId = targetId, - title = title, - messageSource =MessageSource.Group, - )) - return true - } - - suspend fun transEssenceChange( - time: Long, - senderUin: Long, - operatorUin: Long, - msgId: Int, - groupId: Long, - subType: NoticeSubType - ): Boolean { - pushNotice(NoticeEvent( - time = time, - selfId = app.longAccountUin, - postType = PostType.Notice, - type = NoticeType.Essence, - subType = subType, - groupId = groupId, - operatorId = operatorUin, - senderId = senderUin, - msgId = msgId, - messageSource =MessageSource.Group, - )) - return true - } - } - - /** - * 私聊通知 通知器 - */ - 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 - ), - messageSource =MessageSource.Private - )) - 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, - messageSource =MessageSource.Private, - )) - return true - } - - } - - /** - * 请求 通知器 - */ - 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 - )) - return true - } - - suspend fun transGroupApply( - time: Long, - applier: Long, - applierUid: String, - reason: String, - groupCode: Long, - flag: String, - subType: RequestSubType - ): 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 - )) - return true - } - } - - @ShamrockDsl - suspend inline fun onMessageEvent(collector: FlowCollector>) { - messageEventFlow.collect { - GlobalScope.launch { - collector.emit(it) - } - } - } - - @ShamrockDsl - suspend inline fun onNoticeEvent(collector: FlowCollector) { - noticeEventFlow.collect { - GlobalScope.launch { - collector.emit(it) - } - } - } - - @ShamrockDsl - suspend inline fun onRequestEvent(collector: FlowCollector) { - requestEventFlow.collect { - GlobalScope.launch { - collector.emit(it) - } - } - } -} - - - diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/api/HttpTransmitServlet.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/api/HttpTransmitServlet.kt deleted file mode 100644 index a666742f..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/api/HttpTransmitServlet.kt +++ /dev/null @@ -1,65 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.service.api - -import io.ktor.client.network.sockets.ConnectTimeoutException -import io.ktor.client.plugins.HttpRequestTimeoutException -import io.ktor.client.request.header -import io.ktor.client.request.post -import io.ktor.client.request.setBody -import io.ktor.client.statement.HttpResponse -import io.ktor.http.ContentType -import io.ktor.http.contentType -import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig -import moe.fuqiuluo.shamrock.tools.GlobalClient -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.tools.ShamrockVersion -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher -import mqq.app.MobileQQ -import java.net.SocketException - -internal abstract class HttpTransmitServlet : BaseTransmitServlet { - override val address: String by lazy { ShamrockConfig.getWebHookAddress() } - - override fun transmitAccess(): Boolean { - return ShamrockConfig.allowWebHook() - } - - protected suspend inline fun pushTo(body: T): HttpResponse? { - if (!transmitAccess()) return null - try { - if (address.startsWith("http://") || address.startsWith("https://")) { - val response = GlobalClient.post(address) { - contentType(ContentType.Application.Json) - setBody(body) - - header("User-Agent", "Shamrock/$ShamrockVersion") - header("X-QQ-Version", PlatformUtils.getClientVersion(MobileQQ.getContext())) - val runtime = AppRuntimeFetcher.appRuntime - val curUin = runtime.currentAccountUin - header("X-Self-ID", curUin) - header("X-OneBot-Version", "11") - header("X-Impl", "Shamrock") - header("X-Client-Role", "Universal") - header("Sec-WebSocket-Protocol", "11.Shamrock") - } - return if (response.status.value == 204) { - null - } else { - response - } - } else { - LogCenter.log("HTTP推送地址错误: ${address}。", Level.ERROR) - } - } catch (e: ConnectTimeoutException) { - LogCenter.log("HTTP推送失败: 请检查你的推送服务器。", Level.ERROR) - } catch (e: SocketException) { - LogCenter.log("HTTP推送失败: 网络波动。", Level.ERROR) - } catch (e: HttpRequestTimeoutException) { - LogCenter.log("HTTP推送失败: 推送服务器无法连接。", Level.ERROR) - } catch (e: Throwable) { - LogCenter.log("HTTP推送失败: ${e.stackTraceToString()}", Level.ERROR) - } - return null - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/api/WebSocketClientServlet.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/api/WebSocketClientServlet.kt deleted file mode 100644 index c7d90d5b..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/api/WebSocketClientServlet.kt +++ /dev/null @@ -1,192 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) - -package moe.fuqiuluo.shamrock.remote.service.api - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.cancel -import kotlinx.coroutines.launch -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import moe.fuqiuluo.shamrock.remote.action.ActionManager -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.structures.EmptyObject -import moe.fuqiuluo.shamrock.remote.structures.Status -import moe.fuqiuluo.shamrock.remote.structures.resultToString -import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig -import moe.fuqiuluo.shamrock.tools.* -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.remote.service.data.BotStatus -import moe.fuqiuluo.shamrock.remote.service.data.Self -import moe.fuqiuluo.shamrock.remote.service.data.push.MetaEventType -import moe.fuqiuluo.shamrock.remote.service.data.push.MetaSubType -import moe.fuqiuluo.shamrock.remote.service.data.push.PostType -import moe.fuqiuluo.shamrock.remote.service.data.push.PushMetaEvent -import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher -import org.java_websocket.client.WebSocketClient -import org.java_websocket.handshake.ServerHandshake -import java.lang.Exception -import java.net.URI -import kotlin.concurrent.timer -import kotlin.coroutines.CoroutineContext - -internal abstract class WebSocketClientServlet( - private val url: String, - private val heartbeatInterval: Long, - private val wsHeaders: Map -) : BaseTransmitServlet, WebSocketClient(URI(url), wsHeaders), CoroutineScope { - init { - if (connectedClients.containsKey(url)) { - throw RuntimeException("WebSocketClient已存在: $url") - } - } - - private var firstOpen = true - - override fun transmitAccess(): Boolean { - return ShamrockConfig.openWebSocketClient() - } - - override fun onMessage(message: String) { - launch { - handleMessage(message) - } - } - - private suspend fun handleMessage(message: String) { - val respond = kotlin.runCatching { - val actionObject = Json.parseToJsonElement(message).asJsonObject - if (actionObject["post_type"].asStringOrNull == "meta_event") { - // 防止二比把元事件push回来 - return - } - - val action = actionObject["action"].asString - val echo = actionObject["echo"] ?: EmptyJsonString - val params = actionObject["params"].asJsonObjectOrNull ?: EmptyJsonObject - - val handler = ActionManager[action] - handler?.handle(ActionSession(params, echo)) - ?: resultToString( - false, - Status.UnsupportedAction, - EmptyObject, - "不支持的Action", - echo = echo - ) - }.getOrNull() - respond?.let { send(it) } - } - - override fun onOpen(handshakedata: ServerHandshake?) { - LogCenter.log("WebSocketClient onOpen: ${handshakedata?.httpStatus}, ${handshakedata?.httpStatusMessage}") - - connectedClients[url] = this - - pushMetaLifecycle() - if (firstOpen) { - firstOpen = false - } else { - unsubscribe() - } - init() - } - - override fun onClose(code: Int, reason: String?, remote: Boolean) { - if (code == 403) { - if (wsHeaders.containsKey("authorization")) { - val token = wsHeaders["authorization"]!!.substring(7) - LogCenter.log("WebSocketClient连接被拒绝, token: $token 失效", Level.WARN) - } else { - LogCenter.log("WebSocketClient连接被拒绝, 未设置token", Level.WARN) - } - } - LogCenter.log("WebSocketClient onClose: $code, $reason, $remote") - unsubscribe() - coroutineContext.cancel() - connectedClients.remove(url) - } - - override fun onError(ex: Exception?) { - LogCenter.log("WebSocketClient onError: ${ex?.message}") - unsubscribe() - coroutineContext.cancel() - connectedClients.remove(url) - } - - protected suspend inline fun pushTo(body: T) { - if (!transmitAccess() || isClosed || isClosing) return - try { - send(GlobalJson.encodeToString(body)) - } catch (e: Throwable) { - LogCenter.log("被动WS推送失败: ${e.stackTraceToString()}", Level.ERROR) - } - } - - fun startHeartbeatTimer() { - if (heartbeatInterval <= 0) { - LogCenter.log("被动WebSocket心跳间隔为0,不启动心跳", Level.WARN) - return - } - timer( - name = "heartbeat", - initialDelay = heartbeatInterval, - period = heartbeatInterval, - ) { - if (isClosed || isClosing || !isOpen) { - cancel() - return@timer - } - val runtime = AppRuntimeFetcher.appRuntime - LogCenter.log("WebSocketClient心跳: ${app.longAccountUin}", Level.DEBUG) - send(GlobalJson.encodeToString( - PushMetaEvent( - time = System.currentTimeMillis() / 1000, - selfId = app.longAccountUin, - postType = PostType.Meta, - type = MetaEventType.Heartbeat, - subType = MetaSubType.Connect, - status = BotStatus( - Self("qq", runtime.longAccountUin), - runtime.isLogin, - status = "正常", - good = true - ), - interval = heartbeatInterval - ) - ) - ) - } - } - - private fun pushMetaLifecycle() { - launch { - val runtime = AppRuntimeFetcher.appRuntime - val curUin = runtime.currentAccountUin - pushTo( - PushMetaEvent( - time = System.currentTimeMillis() / 1000, - selfId = app.longAccountUin, - postType = PostType.Meta, - type = MetaEventType.LifeCycle, - subType = MetaSubType.Connect, - status = BotStatus( - Self("qq", curUin.toLong()), runtime.isLogin, status = "正常", good = true - ), - interval = heartbeatInterval - ) - ) - } - } - - @OptIn(ExperimentalCoroutinesApi::class) - override val coroutineContext: CoroutineContext = - Dispatchers.IO.limitedParallelism(20) - - companion object { - private val connectedClients = mutableMapOf() - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/api/WebSocketTransmitServlet.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/api/WebSocketTransmitServlet.kt deleted file mode 100644 index 1fb98d00..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/api/WebSocketTransmitServlet.kt +++ /dev/null @@ -1,157 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class, ExperimentalCoroutinesApi::class) - -package moe.fuqiuluo.shamrock.remote.service.api - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.cancel -import kotlinx.coroutines.launch -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import moe.fuqiuluo.shamrock.remote.action.ActionManager -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.structures.EmptyObject -import moe.fuqiuluo.shamrock.remote.structures.Status -import moe.fuqiuluo.shamrock.remote.structures.resultToString -import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig -import moe.fuqiuluo.shamrock.remote.service.data.BotStatus -import moe.fuqiuluo.shamrock.remote.service.data.Self -import moe.fuqiuluo.shamrock.remote.service.data.push.MetaEventType -import moe.fuqiuluo.shamrock.remote.service.data.push.MetaSubType -import moe.fuqiuluo.shamrock.remote.service.data.push.PostType -import moe.fuqiuluo.shamrock.remote.service.data.push.PushMetaEvent -import moe.fuqiuluo.shamrock.tools.GlobalJson -import moe.fuqiuluo.shamrock.tools.* -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher -import org.java_websocket.WebSocket -import org.java_websocket.server.WebSocketServer -import java.net.InetSocketAddress -import java.net.URI -import java.util.Collections -import java.util.Timer -import kotlin.concurrent.timer -import kotlin.coroutines.CoroutineContext - -internal abstract class WebSocketTransmitServlet( - host:String, - port: Int, - protected val heartbeatInterval: Long, -) : BaseTransmitServlet, WebSocketServer(InetSocketAddress(host, port)), CoroutineScope { - private lateinit var heartbeatTask: Timer - protected val eventReceivers: MutableList = Collections.synchronizedList(mutableListOf()) - - init { - connectionLostTimeout = 0 - } - - override val address: String = "-" - - override fun transmitAccess(): Boolean { - return ShamrockConfig.openWebSocket() - } - - inline fun broadcastAnyEvent(any: T) { - broadcastTextEvent(GlobalJson.encodeToString(any)) - } - - fun broadcastTextEvent(text: String) { - broadcast(text, eventReceivers) - } - - init { - if (heartbeatInterval > 0) { - heartbeatTask = timer("heartbeat", true, 0, heartbeatInterval) { - val runtime = AppRuntimeFetcher.appRuntime - val curUin = runtime.currentAccountUin - LogCenter.log("WebSocket心跳: $curUin", Level.DEBUG) - broadcastAnyEvent( - PushMetaEvent( - time = System.currentTimeMillis() / 1000, - selfId = app.longAccountUin, - postType = PostType.Meta, - type = MetaEventType.Heartbeat, - subType = MetaSubType.Connect, - status = BotStatus( - Self("qq", curUin.toLong()), - runtime.isLogin, - status = "正常", - good = true - ), - interval = heartbeatInterval - ) - ) - } - } else { - LogCenter.log("主动WebSocket心跳间隔为0,不启动心跳", Level.WARN) - } - } - - override fun onClose(conn: WebSocket, code: Int, reason: String, remote: Boolean) { - val path = URI.create(conn.resourceDescriptor).path - if (path != "/api") { - eventReceivers.remove(conn) - } - runCatching { - conn.remoteSocketAddress.address.hostAddress to conn.remoteSocketAddress.port - }.onSuccess { - LogCenter.log({ "WSServer断开(${it.first}:${it.second}$path): $code,$reason,$remote" }, Level.WARN) - }.onFailure { - LogCenter.log({ "WSServer断开($path): $code,$reason,$remote" }, Level.WARN) - } - } - - override fun onMessage(conn: WebSocket, message: String) { - val path = URI.create(conn.resourceDescriptor).path - launch { - onHandleAction(conn, message, path) - } - } - - private suspend fun onHandleAction(conn: WebSocket, message: String, path: String) { - val respond = kotlin.runCatching { - val actionObject = Json.parseToJsonElement(message).asJsonObject - if (actionObject["post_type"].asStringOrNull == "meta_event") { - // 防止二比把元事件push回来 - return - } - val action = actionObject["action"].asString - val echo = actionObject["echo"] ?: EmptyJsonString - val params = actionObject["params"].asJsonObjectOrNull ?: EmptyJsonObject - - val handler = ActionManager[action] - handler?.handle(ActionSession(params, echo)) - ?: resultToString(false, Status.UnsupportedAction, EmptyObject, "不支持的Action", echo = echo) - }.getOrNull() - respond?.let { conn.send(it) } - } - - override fun onError(conn: WebSocket, ex: Exception?) { - LogCenter.log("WSServer Error: " + ex?.stackTraceToString(), Level.ERROR) - unsubscribe() - coroutineContext.cancel() - if (::heartbeatTask.isInitialized) { - heartbeatTask.cancel() - } - } - - override fun onStart() { - LogCenter.log("WSServer start running on ws://${getAddress()}!") - init() - } - - protected inline fun pushTo(body: T) { - if(!transmitAccess()) return - try { - broadcastTextEvent(GlobalJson.encodeToString(body)) - } catch (e: Throwable) { - LogCenter.log("WS推送失败: ${e.stackTraceToString()}", Level.ERROR) - } - } - - override val coroutineContext: CoroutineContext = - Dispatchers.IO.limitedParallelism(40) - } \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/config/Config.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/config/Config.kt deleted file mode 100644 index 72f2b749..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/config/Config.kt +++ /dev/null @@ -1,41 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.service.config - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class ServiceConfig( - @SerialName("rules") val rules: Rules? = null, - @SerialName("default_token") var defaultToken: String? = null, - @SerialName("active_websocket") var activeWebSocket: ConnectionConfig? = null, - @SerialName("passive_websocket") var passiveWebSocket: MutableList? = null, - @SerialName("allow-temp-session") var allowTempSession: Boolean = false, - @SerialName("anti_qq_trace") var antiTrace: Boolean = true -) - -@Serializable -data class ConnectionConfig( - @SerialName("address") val address: String? = null, - @SerialName("port") var port: Int? = null, - @SerialName("token") val token: String? = null, - @SerialName("tokens") val tokens: List? = null, - @SerialName("heartbeat_interval") var heartbeatInterval: Long? = null, -) - -@Serializable -data class Rules( - @SerialName("group_rule") val groupRule: GroupRule? = null, - @SerialName("private_rule") val privateRule: PrivateRule? = null -) - -@Serializable -data class GroupRule( - @SerialName("black_list") val black: List? = null, - @SerialName("white_list") val white: List? = null, -) - -@Serializable -data class PrivateRule( - @SerialName("black_list") val black: List? = null, - @SerialName("white_list") val white: List? = null, -) \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/config/ShamrockConfig.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/config/ShamrockConfig.kt deleted file mode 100644 index 1225e7cf..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/config/ShamrockConfig.kt +++ /dev/null @@ -1,284 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.service.config - -import android.content.Intent -import com.tencent.mmkv.MMKV -import de.robv.android.xposed.XposedBridge -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.encodeToString -import moe.fuqiuluo.qqinterface.servlet.transfile.NtV2RichMediaSvc -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.tools.GlobalJson5 -import moe.fuqiuluo.shamrock.utils.MMKVFetcher -import mqq.app.MobileQQ -import java.io.File - -internal object ShamrockConfig { - private val ConfigDir = MobileQQ.getContext().getExternalFilesDir(null)!! - .parentFile!!.resolve("Tencent/Shamrock").also { - if (it.exists()) it.delete() - it.mkdirs() - } - private val config = kotlin.runCatching { - GlobalJson5.decodeFromString(ConfigDir.resolve("config.json").also { - if (!it.exists()) it.writeText("{}") - }.readText()) - }.onFailure { - LogCenter.log("您的配置文件出现错误: ${it.stackTraceToString()}", Level.ERROR) - }.getOrElse { - ServiceConfig() - } - - fun isInit(): Boolean { - val mmkv = MMKVFetcher.mmkvWithId("shamrock_config") - return mmkv.getBoolean("isInit", false) - } - - private fun updateConfig(config: ServiceConfig = this.config) { - ConfigDir.resolve("config.json").writeText(GlobalJson5.encodeToString(config)) - } - - fun updateConfig(intent: Intent) { - mmkv.apply { - if (!intent.getBooleanExtra("disable_auto_sync_setting", false)) { - putBoolean("tablet", intent.getBooleanExtra("tablet", false)) // 强制平板模式 - putInt("port", intent.getIntExtra("port", 5700)) // 主动HTTP端口 - putBoolean("ws", intent.getBooleanExtra("ws", false)) // 主动WS开关 - putBoolean("http", intent.getBooleanExtra("http", false)) // HTTP回调开关 - putString("http_addr", intent.getStringExtra("http_addr")) // WebHook回调地址 - putBoolean("ws_client", intent.getBooleanExtra("ws_client", false)) // 被动WS开关 - putBoolean("use_cqcode", intent.getBooleanExtra("use_cqcode", false)) // 使用CQ码 - putBoolean("inject_packet", intent.getBooleanExtra("inject_packet", false)) // 拦截无用包 - putBoolean("debug", intent.getBooleanExtra("debug", false)) // 调试模式 - putString( "key_store", intent.getStringExtra("key_store")) // 证书路径 - putString( "ssl_pwd", intent.getStringExtra("ssl_pwd")) // 证书密码 - putString( "ssl_private_pwd", intent.getStringExtra("ssl_private_pwd")) // 证书私钥密码 - putString( "ssl_alias", intent.getStringExtra("ssl_alias")) // 证书别名 - putInt( "ssl_port", intent.getIntExtra("ssl_port", 5701)) // 主动HTTP端口 - putBoolean("alive_reply", intent.getBooleanExtra("alive_reply", false)) // 自回复测试 - putBoolean("enable_self_msg", intent.getBooleanExtra("enable_self_msg", false)) // 推送自己发的消息 - putBoolean("shell", intent.getBooleanExtra("shell", false)) // 开启Shell接口 - putBoolean("enable_sync_msg_as_sent_msg", intent.getBooleanExtra("enable_sync_msg_as_sent_msg", false)) // 推送同步消息 - putBoolean("forbid_useless_process", intent.getBooleanExtra("forbid_useless_process", false)) // 禁用QQ生成无用进程 - putBoolean("enable_old_bdh", intent.getBooleanExtra("enable_old_bdh", false)) // 启用旧版BDH - intent.getStringExtra("up_res_group")?.let { putString("up_res_group", it) } - } else { - XposedBridge.log("[Shamrock] 已禁用自动同步配置") - } - config.defaultToken = intent.getStringExtra("token") - config.antiTrace = intent.getBooleanExtra("anti_qq_trace", true) - val wsPort = intent.getIntExtra("ws_port", 5800) - config.activeWebSocket = if (config.activeWebSocket == null) ConnectionConfig( - address = "0.0.0.0", - port = wsPort, - ) else config.activeWebSocket?.also { - it.port = wsPort - } - config.passiveWebSocket = intent.getStringExtra("ws_addr")?.split(",", "|", ",")?.filter { address -> - address.isNotBlank() && (address.startsWith("ws://") || address.startsWith("wss://")) - }?.map { - ConnectionConfig(address = it) - }?.toMutableList() - putBoolean("isInit", true) - } - if (!intent.getBooleanExtra("disable_auto_sync_setting", false)) { - updateConfig() - } - } - - fun putDefaultSettings() { - val mmkv = MMKVFetcher.mmkvWithId("shamrock_config") - if ((!isInit()) && (!mmkv.getBoolean("isEmergencyInitBefore", false))){ - mmkv.apply { - putBoolean("tablet", false) // 强制平板模式 - putInt("port", 5700) // 主动HTTP端口 - putBoolean("ws", false) // 主动WS开关 - putBoolean("http", false) // HTTP回调开关 - putString("http_addr", null) // WebHook回调地址 - putBoolean("ws_client", false) // 被动WS开关 - putBoolean("use_cqcode", false) // 使用CQ码 - putBoolean("inject_packet", false) // 拦截无用包 - putBoolean("debug", false) // 调试模式 - putString("key_store", null) // 证书路径 - putString("ssl_pwd", null) // 证书密码 - putString("ssl_private_pwd", null) // 证书私钥密码 - putString("ssl_alias", null) // 证书别名 - putInt("ssl_port", 5701) // 主动HTTP端口 - putBoolean("alive_reply", true) // 自回复测试 - putBoolean("enable_self_msg", false) // 推送自己发的消息 - putBoolean("shell", false) // 开启Shell接口 - putBoolean("enable_sync_msg_as_sent_msg", true) // 推送同步消息 - putBoolean("forbid_useless_process", false) // 禁用QQ生成无用进程 - putBoolean("enable_old_bdh", false) // 启用旧版BDH - putBoolean("antiTrace", false) - putBoolean("super_anti", true) - putString("up_res_group", "") - - config.defaultToken = null - // config.antiTrace = true - val wsPort = 5800 - config.activeWebSocket = - if (config.activeWebSocket == null) ConnectionConfig( - address = "0.0.0.0", - port = wsPort, - ) else config.activeWebSocket?.also { - it.port = wsPort - } - config.passiveWebSocket = null - putBoolean("isInit", true) - putBoolean("isEmergencyInitBefore", true) - } - XposedBridge.log("[Shamrock] 强制初始化配置完成,请重新打开QQ") - } else { - XposedBridge.log("[Shamrock] 程序逻辑错误或错误地多次强制初始化") - mmkv.putBoolean("isEmergencyInitBefore", false) - XposedBridge.log("[Shamrock] 如果执意要再次强制初始化,请重新执行程序") - } - } - - private val mmkv: MMKV - get() = MMKVFetcher.mmkvWithId("shamrock_config") - - fun getUpResGroup(): String { - return mmkv.getString("up_res_group", "") ?: "" - } - - fun aliveReply(): Boolean { - return mmkv.getBoolean("alive_reply", false) - } - - fun allowTempSession(): Boolean { - return config.allowTempSession - } - - fun getGroupMsgRule(): GroupRule? { - return config.rules?.groupRule - } - - fun getPrivateRule(): PrivateRule? { - return config.rules?.privateRule - } - - fun enableSyncMsgAsSentMsg(): Boolean { - return mmkv.getBoolean("enable_sync_msg_as_sent_msg", false) - } - - fun enableSelfMsg(): Boolean { - return mmkv.getBoolean("enable_self_msg", false) - } - - fun forbidUselessProcess(): Boolean { - return mmkv.getBoolean("forbid_useless_process", false) - } - - fun openWebSocketClient(): Boolean { - return mmkv.getBoolean("ws_client", false) - } - - fun getWebSocketClientAddress(): List { - return config.passiveWebSocket ?: emptyList() - } - - fun openWebSocket(): Boolean { - return mmkv.getBoolean("ws", false) - } - - fun getActiveWebSocketConfig(): ConnectionConfig? { - return config.activeWebSocket - } - - fun getToken(): String { - return config.defaultToken ?: "" - } - - fun useCQ(): Boolean { - return mmkv.getBoolean("use_cqcode", false) - } - - fun allowWebHook(): Boolean { - return mmkv.getBoolean("http", false) - } - - fun getWebHookAddress(): String { - return mmkv.getString("http_addr", "") ?: "" - } - - fun forceTablet(): Boolean { - return mmkv.getBoolean("tablet", true) - } - - fun getPort(): Int { - return mmkv.getInt("port", 5700) - } - - fun isInjectPacket(): Boolean { - return mmkv.getBoolean("inject_packet", false) - } - - fun enableOldBDH(): Boolean { - return mmkv.getBoolean("enable_old_bdh", false) - } - - fun isDebug(): Boolean { - return mmkv.getBoolean("debug", false) - } - - fun ssl(): Boolean { - return getKeyStorePath()?.exists() == true - } - - fun getKeyStorePath(): File? { - mmkv.getString("key_store", null)?.let { - return File(it) - } - return null - } - - fun sslPwd(): CharArray? { - return mmkv.getString("ssl_pwd", null)?.toCharArray() - } - - fun sslPrivatePwd(): String? { - return mmkv.getString("ssl_private_pwd", null) - } - - fun sslAlias(): String? { - return mmkv.getString("ssl_alias", null) - } - - fun getSslPort(): Int { - return mmkv.getInt("ssl_port", getPort()) - } - - fun isDev(): Boolean { - return mmkv.getBoolean("dev", false) - } - - operator fun set(key: String, value: String) { - mmkv.putString(key, value) - } - - operator fun set(key: String, value: Boolean) { - mmkv.putBoolean(key, value) - } - - operator fun set(key: String, value: Int) { - mmkv.putInt(key, value) - } - - operator fun set(key: String, value: Long) { - mmkv.putLong(key, value) - } - - operator fun set(key: String, value: Float) { - mmkv.putFloat(key, value) - } - - fun isAntiTrace(): Boolean { - return config.antiTrace - } - - fun allowShell(): Boolean { - return mmkv.getBoolean("shell", false) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/BotStatus.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/BotStatus.kt deleted file mode 100644 index 6443de6d..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/BotStatus.kt +++ /dev/null @@ -1,30 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.service.data - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class BotStatus( - val self: Self, - val online: Boolean, - val good: Boolean, - @SerialName("qq.status") - val status: String -) - -@Serializable -data class Self( - val platform: String, - @SerialName("user_id") - val userId: Long -) - -@Serializable -data class UserDetail( - @SerialName("user_id") - val userId: Long, - @SerialName("user_name") - val userName: String, - @SerialName("user_displayname") - val userDisplayName: String -) \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/FriendData.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/FriendData.kt deleted file mode 100644 index 51fbc1ea..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/FriendData.kt +++ /dev/null @@ -1,37 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.service.data - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -internal data class FriendEntry( - @SerialName("user_id") val id: Long = 0, - @SerialName("user_name") val name: String?, - @SerialName("user_displayname") val displayName: String?, - @SerialName("user_remark") val remark: String?, - val age: Int, - val gender: Byte, - @SerialName("group_id") val groupId: Int, - @SerialName("platform") val platformType: PlatformType, - @SerialName("term_type") val termType: Int, -) - - -@Serializable -internal data class FriendRequest( - @SerialName("request_id") val seq: Long = 0, - @SerialName("requester_uin") val userId: Long = 0, - @SerialName("requester_nick") val name: String?, - val source: String?, - @SerialName("sub_id") val subId: Int?, - @SerialName("sub_src_id") val subSrcId: Int?, - @SerialName("message") val msg: String?, - @SerialName("source_group_name") val sourceGroupName: String?, - @SerialName("source_group_id") val sourceGroupCode: Long?, - val flag: String, - val sex: String?, - val age: Int?, - @SerialName("msg_detail") val msgDetail: String?, - val status: String?, - -) \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/GroupAnnouncement.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/GroupAnnouncement.kt deleted file mode 100644 index 402ef965..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/GroupAnnouncement.kt +++ /dev/null @@ -1,25 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.service.data - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -internal data class GroupAnnouncement ( - @SerialName("sender_id") val senderId: Long = 0, - @SerialName("publish_time") val publishTime: Long, - @SerialName("message") val message: GroupAnnouncementMessage, -) - -@Serializable -internal data class GroupAnnouncementMessage ( - @SerialName("text") val text: String, - @SerialName("images") val images: List, -) - -@Serializable -internal data class GroupAnnouncementMessageImage ( - @SerialName("height") val height: String, - @SerialName("width") val width: String, - @SerialName("id") val id: String, -) - diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/GroupHonor.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/GroupHonor.kt deleted file mode 100644 index 7cd58c9a..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/GroupHonor.kt +++ /dev/null @@ -1,39 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.service.data - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -internal const val HONOR_TALKATIVE = 1 -internal const val HONOR_GROUP_FIRE = 2 -internal const val HONOR_GROUP_FLAME = 4 -internal const val HONOR_NEWBIE = 5 -internal const val HONOR_HAPPY = 6 -internal const val HONOR_ACADEMIC_STAR = 7 -internal const val HONOR_TOP_STUDENT = 8 -internal const val HONOR_TOP_GOD = 9 -internal const val HONOR_LEADING = 10 -internal const val HONOR_NEWBIE_2 = 11 -internal const val HONOR_ATMOSPHERE = 12 -internal const val HONOR_GIFT = 13 - -@Serializable -data class GroupMemberHonor( - @SerialName("user_id") val userId: Long, - @SerialName("nickname") var nick: String, - @SerialName("avatar") val avatar: String, - @SerialName("day_count") val dayCount: Int, - @SerialName("id") val id: Int, - @SerialName("description") val desc: String, -) - -@Serializable -internal data class GroupAllHonor( - @SerialName("group_id") val groupId: Long, - @SerialName("current_talkative") val currentTalkActive: GroupMemberHonor?, - @SerialName("talkative_list") val talkativeList: List?, - @SerialName("performer_list") val performerList: List?, - @SerialName("legend_list") val legendList: List?, - @SerialName("strong_newbie_list") val strongNewbieList: List?, - @SerialName("emotion_list") val emotionList: List?, - @SerialName("all") val all: List?, -) diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/Message.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/Message.kt deleted file mode 100644 index 91937299..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/Message.kt +++ /dev/null @@ -1,70 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.service.data - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement - -@Serializable -internal data class MessageResult( - @SerialName("message_id") val msgId: Int, - @SerialName("time") val time: Long -) - -@Serializable -internal data class UploadForwardMessageResult( - @SerialName("res_id") val resId: String, - @SerialName("filename") val filename: String, - @SerialName("summary") val summary: String, - @SerialName("desc") val desc: String, -) - -@Serializable -internal data class SendForwardMessageResult( - @SerialName("message_id") val msgId: Int, - @SerialName("res_id") val resId: String, - @SerialName("forward_id") val forwardId: String = resId -) - -@Serializable -internal data class MessageDetail( - @SerialName("time") val time: Int, - @SerialName("message_type") val msgType: String, - @SerialName("message_id") val msgId: Int, - @SerialName("message_id_qq") val qqMsgId: Long, - @SerialName("message_seq") val msgSeq: Long, - @SerialName("real_id") val realId: Long, - @SerialName("sender") val sender: MessageSender, - @SerialName("message") val message: List>, - @SerialName("group_id") val groupId: Long = 0, - @SerialName("peer_id") val peerId: Long, - @SerialName("target_id") val targetId: Long = 0, -) - -@Serializable -internal data class GetForwardMsgResult( - @SerialName("messages") val msgs: List -) - -@Serializable -internal data class MessageSender( - @SerialName("user_id") val userId: Long, - @SerialName("nickname") val nickName: String, - @SerialName("sex") val sex: String, - @SerialName("age") val age: Int, - @SerialName("uid") val uid: String, - @SerialName("tiny_id") val tinyId: String, -) - -@Serializable -internal data class EssenceMessage( - @SerialName("sender_id") val senderId: Long, - @SerialName("sender_nick") val senderNick: String, - @SerialName("sender_time") val senderTime: Long, - @SerialName("operator_id") val operatorId: Long, - @SerialName("operator_nick") val operatorNick: String, - @SerialName("operator_time") val operatorTime: Long, - @SerialName("message_id") var messageId: Int, - @SerialName("message_seq") val messageSeq: Int, - @SerialName("real_id") val realId: Int, - @SerialName("message_content") val messageContent: JsonElement, -) \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/Platform.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/Platform.kt deleted file mode 100644 index da7e2264..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/Platform.kt +++ /dev/null @@ -1,68 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.service.data - -internal enum class PlatformType { - ANDROID_PAD, - AOL_CHAOJIHUIYUAN, - AOL_HUIYUAN, - AOL_SQQ, - CAR, - HRTX_IPHONE, - HRTX_PC, - MC_3G, - MISRO_MSG, - MOBILE_ANDROID, - MOBILE_ANDROID_NEW, - MOBILE_HD, - MOBILE_HD_NEW, - MOBILE_IPAD, - MOBILE_IPAD_NEW, - MOBILE_IPHONE, - MOBILE_OTHER, - MOBILE_PC, - MOBILE_WINPHONE_NEW, - QQ_FORELDER, - QQ_SERVICE, - TV_QQ, - WIN8, - MAC, - WINDOWS, - PC_OR_MAC_OR_LINUX, - WATCH, - WINPHONE; - - companion object { - fun valueOf(termType: Int): PlatformType { - return when(termType) { - 68104 -> ANDROID_PAD - 73730 -> AOL_CHAOJIHUIYUAN - 73474 -> AOL_HUIYUAN - 69378 -> AOL_SQQ - 65806 -> CAR - 66566 -> HRTX_IPHONE - 66561 -> HRTX_PC - 65795 -> MC_3G - 69634 -> MISRO_MSG - 65799 -> MOBILE_ANDROID - 72450 -> MOBILE_ANDROID_NEW - 65805 -> MOBILE_HD - 71426 -> MOBILE_HD_NEW - 68361 -> MOBILE_IPAD - 72194 -> MOBILE_IPAD_NEW - 67586 -> MOBILE_IPHONE - 65794 -> MOBILE_OTHER - 65793 -> MOBILE_PC - 81154, 66818, 66831 -> MAC - 77313 -> WINDOWS - 83714 -> PC_OR_MAC_OR_LINUX - 72706 -> MOBILE_WINPHONE_NEW - 70922 -> QQ_FORELDER - 71170 -> QQ_SERVICE - 69130 -> TV_QQ - 69899 -> WIN8 - 65804 -> WINPHONE - 78082, 78096, 75023 -> WATCH - else -> MOBILE_OTHER - } - } - } -} diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/ResourceData.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/ResourceData.kt deleted file mode 100644 index a0607128..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/ResourceData.kt +++ /dev/null @@ -1,16 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.service.data - -import kotlinx.serialization.Serializable -import java.util.Base64 - -@Serializable internal data class OutResource( - val file: String, - val url: String, - val md5: String, -) - -@Serializable internal data class OutResourceByBase64( - val file: String, - val base64String: String, - val md5: String, -) \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/TicketData.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/TicketData.kt deleted file mode 100644 index b0d69c78..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/TicketData.kt +++ /dev/null @@ -1,17 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.service.data - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -internal data class Credentials( - @SerialName("token") val bkn: String = "", - @SerialName("cookies") val cookie: String = "", - @SerialName("bigdata_ticket") val bigDataTicket: BigDataTicket? = null -) - -@Serializable -data class BigDataTicket( - var key: String? = null, - var sig: String? = null -) \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/TroopData.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/TroopData.kt deleted file mode 100644 index b78f21fd..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/TroopData.kt +++ /dev/null @@ -1,70 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.service.data - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import moe.fuqiuluo.shamrock.remote.service.data.push.MemberRole - -@Serializable -internal data class SimpleTroopInfo( - @SerialName("group_id") val groupId: Long, - @SerialName("group_name") val groupName: String?, - @SerialName("group_remark") val groupRemark: String?, - @SerialName("group_uin") val groupUin: Long, - @SerialName("admins") val adminList: List, - @SerialName("class_text") val classText: String?, - @SerialName("is_frozen") val isFrozen: Boolean, - //@SerialName("troop_level") val troopLevel: String?, - @SerialName("max_member") val maxMember: Int, - @SerialName("member_num") val memNum: Int, - @SerialName("member_count") val memCount: Int, - @SerialName("max_member_count") val maxNum: Int, -) - -@Serializable -internal data class SimpleTroopMemberInfo( - @SerialName("user_id") val uin: Long, - @SerialName("group_id") val groupId: Long, - @SerialName("user_name") val name: String, - @SerialName("sex") val sex: String, - @SerialName("age") val age: Int, - @SerialName("title") val title: String, - @SerialName("title_expire_time") val titleExpireTime: Int, - @SerialName("nickname") val nick: String, - @SerialName("user_displayname") val showName: String?, - @SerialName("card") val cardName: String?, - @SerialName("distance") val distance: Int, - @SerialName("honor") val honor: List, - @SerialName("join_time") val joinTime: Long, - @SerialName("last_active_time") val lastActiveTime: Long, - @SerialName("last_sent_time") val lastSentTime: Long, - @SerialName("unique_name") val uniqueName: String?, - @SerialName("area") val area: String, - @SerialName("level") val level: Int, - @SerialName("role") val role: MemberRole, - @SerialName("unfriendly") val unfriendly: Boolean, - @SerialName("card_changeable") val cardChangeable: Boolean, - @SerialName("shut_up_timestamp") val shutUpTimestamp: Long?, -) - -@Serializable -internal data class GroupRequest( - // InvitedRequest - @SerialName("request_id") val msgSeq: Long, - @SerialName("invitor_uin") val invitorUin: Long?, - @SerialName("invitor_nick") val invitorNick: String?, - @SerialName("group_id") val groupId: Long, - @SerialName("group_name") val groupName: String, - @SerialName("checked") val checked: Boolean, - @SerialName("actor") val actor: Long, - // JoinRequest - @SerialName("requester_uin") val requesterUin: Long, - @SerialName("requester_nick") val requesterNick: String, - @SerialName("message") val message: String, - @SerialName("flag") val flag: String, -) - -@Serializable -internal data class GroupSystemMessage( - @SerialName("invited_requests") var invited: List, - @SerialName("join_requests") var join: List, -) \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/VersionInfo.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/VersionInfo.kt deleted file mode 100644 index 480686f8..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/VersionInfo.kt +++ /dev/null @@ -1,18 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.service.data - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class VersionInfo( - @SerialName("app_full_name") - val appFullName: String, - @SerialName("app_name") - val appName: String, - @SerialName("app_version") - val appVersion: String, - val impl: String, - val version: String, - @SerialName("onebot_version") - val onebotVersion: String -) diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/VipData.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/VipData.kt deleted file mode 100644 index 965268c0..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/VipData.kt +++ /dev/null @@ -1,22 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.service.data - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class VipInfo( - val type: VipType, - val level: Int, - @SerialName("vip_type") val vipType: Int, - @SerialName("template_id") val templateId: Long -) - -enum class VipType { - QQ_VIP, - SUPER_QQ, - SUPER_VIP, - QQ_VIDEO, - QQ_READING, - BIG_VIP, - YELLOW_VIP -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/profile/ProfileCard.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/profile/ProfileCard.kt deleted file mode 100644 index a58ebb19..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/profile/ProfileCard.kt +++ /dev/null @@ -1,37 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.service.data.profile - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import moe.fuqiuluo.shamrock.remote.service.data.VipInfo - -@Serializable -data class ProfileCard( - @SerialName("user_id") val uin: Long, - @SerialName("user_name") val name: String, - @SerialName("user_displayname") val displayName: String?, - @SerialName("user_remark") val remark: String?, - @SerialName("mail") val mail: String?, - @SerialName("find_method") val findMethod: String?, - @SerialName("max_vote_cnt") val maxVoteCnt: Short, - @SerialName("have_vote_cnt") val haveVoteCnt: Short, - @SerialName("vip_list") val vipList: List, - @SerialName("hobby_entry") val hobbyEntry: String?, - @SerialName("level") val level: Int, - @SerialName("birthday") val birthday: Long, - @SerialName("login_day") val loginDay: Long, - @SerialName("vote_cnt") val voteCnt: Long, - @SerialName("qid") val qid: String, - @SerialName("is_school_verified") val schoolVerified: Boolean, - @SerialName("location") val location: Location, - @SerialName("cookie") val cookie: ByteArray, -) - -@Serializable -data class Location( - val city: String?, - val company: String?, - val country: String?, - val province: String?, - val hometown: String?, - val school: String? -) diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/profile/ProfileProtocolConst.java b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/profile/ProfileProtocolConst.java deleted file mode 100644 index 202c4d0f..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/profile/ProfileProtocolConst.java +++ /dev/null @@ -1,41 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.service.data.profile; - -public interface ProfileProtocolConst { - public static final String CMD_GET_PROFILE_DETAIL = "OidbSvc.0x480_9_IMCore"; - public static final String CMD_SET_PROFILE_DETAIL = "OidbSvc.0x4ff_9_IMCore"; - public static final String KET_INTERESTS = "interests"; - public static final String KEY_AGE = "age"; - public static final String KEY_BIRTHDAY = "birthday"; - public static final String KEY_COLLEGE = "college"; - public static final String KEY_COMPANY = "company"; - public static final String KEY_CONSTELLATION = "key_constellation"; - public static final String KEY_EMAIL = "email"; - public static final String KEY_HOMETOWN = "hometown"; - public static final String KEY_HOMETOWN_DESC = "hometown_desc"; - public static final String KEY_LOCATION = "location"; - public static final String KEY_LOCATION_DESC = "location_desc"; - public static final String KEY_LOCATION_NAME = "location_name"; - public static final String KEY_NICK = "nick"; - public static final String KEY_PARSE_PROFILE_LOCATION = "parse_profile_location"; - public static final String KEY_PERSONAL_NOTE = "personalNote"; - public static final String KEY_PROFESSION = "profession"; - public static final String KEY_SEX = "sex"; - public static final String PARAM_ADD_FRIEND_SOURCE = "addFriendSource"; - public static final String PARAM_COME_FROM_TYPE = "comeFromType"; - public static final String PARAM_GET_CONTROL = "getControl"; - public static final String PARAM_IS_FRIEND = "isFriend"; - public static final String PARAM_LOGIN_SIG = "loginSig"; - public static final String PARAM_QZONE_FEED_TIMESTAMP = "qZoneFeedTimeStamp"; - public static final String PARAM_QZONE_SEED = "qZoneSeed"; - public static final String PARAM_REQ_0X5EB = "req0x5ebList"; - public static final String PARAM_REQ_EXTEND = "reqExtendFriend"; - public static final String PARAM_REQ_MEDAL = "reqMedalWall"; - public static final String PARAM_REQ_SERVICES = "reqServiceList"; - public static final String PARAM_REQ_TEMPLATE = "reqTemplate"; - public static final String PARAM_SEARCH_NAME = "searchName"; - public static final String PARAM_SECURE_SIG = "secureSig"; - public static final String PARAM_SELF_UIN = "selfUin"; - public static final String PARAM_TARGET_UIN = "targetUin"; - public static final String PARAM_TROOP_CODE = "troopCode"; - public static final String PARAM_TROOP_UIN = "troopUin"; -} diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/push/MessageEvent.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/push/MessageEvent.kt deleted file mode 100644 index bc62ad88..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/push/MessageEvent.kt +++ /dev/null @@ -1,106 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.service.data.push - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement - - -@Serializable -internal enum class MsgSubType { - /** - * 群聊子类型 - */ - @SerialName("normal") NORMAL, - @SerialName("anonymous") ANONYMOUS, - @SerialName("notice") NOTICE, - @SerialName("group_self") GroupSelf, - - /** - * 私聊子类型 - */ - @SerialName("group") GroupLess, - @SerialName("friend") Friend, - @SerialName("other") Other, - - @SerialName("channel") Channel -} - -@Serializable -internal enum class MsgType { - @SerialName("group") Group, - @SerialName("private") Private, - @SerialName("guild") Guild -} - -@Serializable -internal enum class PostType { - @SerialName("meta_event") Meta, - @SerialName("notice") Notice, - @SerialName("request") Request, - @SerialName("message") Msg, - @SerialName("message_sent") MsgSent, -} - -/** - * 不要使用继承的方式实现通用字段,那样会很难维护! - */ -@Serializable -internal data class MessageEvent ( - @SerialName("time") val time: Long, - @SerialName("self_id") val selfId: Long, - @SerialName("post_type") val postType: PostType, - @SerialName("message_type") val messageType: MsgType, - @SerialName("sub_type") val subType: MsgSubType, - @SerialName("message_id") val messageId: Int, - @SerialName("group_id") val groupId: Long = Long.MIN_VALUE, - @SerialName("guild_id") val guildId: String? = null, - @SerialName("channel_id") val channelId: String? = null, - @SerialName("target_id") val targetId: Long = Long.MIN_VALUE, - @SerialName("peer_id") val peerId: Long, - @SerialName("user_id") val userId: Long, - @SerialName("anonymous") val anonymous: Anonymous? = null, - @SerialName("message") val message: JsonElement, - @SerialName("raw_message") val rawMessage: String, - @SerialName("font") val font: Int, - @SerialName("sender") val sender: Sender, - @SerialName("temp_source") val tmpSource: Int = Int.MIN_VALUE, - @SerialName("from_nick") val fromNickName: String? = null -) - -enum class MessageTempSource(val id: Int) { - Group(0), - Consultation(1), - Seek(2), - QQMovie(3), - HotChat(4), - VerifyMsg(6), - Discussion(7), - Dating(8), - Contact(9), - Unknown(-1), -} - -@Serializable -internal data class Anonymous( - @SerialName("name") val name: String -) - -@Serializable -internal enum class MemberRole { - @SerialName("owner") Owner, - @SerialName("admin") Admin, - @SerialName("member") Member, - @SerialName("stranger") Stranger, - @SerialName("unknown") Unknown -} - -@Serializable -internal data class Sender( - @SerialName("user_id") val userId: Long, - @SerialName("nickname") val nickname: String, - @SerialName("card") val card: String, - @SerialName("role") val role: MemberRole?, - @SerialName("title") val title: String, - @SerialName("level") val level: String, - @SerialName("tiny_id") val tinyId: String = "0", -) \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/push/NoticeEvent.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/push/NoticeEvent.kt deleted file mode 100644 index cf333566..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/push/NoticeEvent.kt +++ /dev/null @@ -1,154 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.service.data.push - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -internal enum class NoticeType { - @SerialName("group_ban") GroupBan, - @SerialName("group_admin") GroupAdminChange, - @SerialName("group_decrease") GroupMemDecrease, - @SerialName("group_increase") GroupMemIncrease, - @SerialName("group_recall") GroupRecall, - @SerialName("group_card") GroupCard, - @SerialName("essence") Essence, - @SerialName("friend_recall") FriendRecall, - @SerialName("notify") Notify, - @SerialName("group_upload") GroupUpload, - @SerialName("private_upload") PrivateUpload -} - -@Serializable -internal enum class RequestType { - @SerialName("friend") Friend, - @SerialName("group") Group, -} - -@Serializable -internal enum class NoticeSubType { - @SerialName("none") None, - @SerialName("ban") Ban, - @SerialName("lift_ban") LiftBan, - @SerialName("set") Set, - @SerialName("unset") UnSet, - @SerialName("add") Add, - @SerialName("invite") Invite, - @SerialName("approve") Approve, - @SerialName("leave") Leave, - @SerialName("kick") Kick, - @SerialName("kick_me") KickMe, - @SerialName("poke") Poke, - @SerialName("sign") Sign, - @SerialName("title") Title, - @SerialName("delete") Delete, -} - -@Serializable -internal enum class RequestSubType { - @SerialName("none") None, - @SerialName("add") Add, - @SerialName("invite") Invite, -} - -@Serializable -internal enum class MessageSource { - @SerialName("group") Group, - - @SerialName("private") Private, -} - -/** - * 不要使用继承的方式实现通用字段,那样会很难维护! - */ -@Serializable -internal data class NoticeEvent( - @SerialName("time") val time: Long, - @SerialName("self_id") val selfId: Long, - @SerialName("post_type") val postType: PostType, - @SerialName("notice_type") val type: NoticeType, - @SerialName("sub_type") val subType: NoticeSubType = NoticeSubType.None, - @SerialName("group_id") val groupId: Long = Long.MIN_VALUE, - @SerialName("operator_id") val operatorId: Long = Long.MIN_VALUE, - @SerialName("operator_uid") val operatorUid: String = "", - @SerialName("user_id") val userId: Long = Long.MIN_VALUE, - @SerialName("user_uid") val userUid: String = "", - @SerialName("sender_id") val senderId: Long = Long.MIN_VALUE, - @SerialName("duration") val duration: Int = Int.MIN_VALUE, - @SerialName("message_id") val msgId: Int = Int.MIN_VALUE, - @SerialName("tip_text") val tip: String = "", - - @SerialName("target_id") val target: Long = Long.MIN_VALUE, - @SerialName("target_uid") val targetUid: String = "", - - - @SerialName("file") val file: GroupFileMsg? = null, - @SerialName("private_file") val privateFile: PrivateFileMsg? = null, - @SerialName("flag") val flag: String? = null, - - // 群名片 - @SerialName("card_new") val cardNew: String? = null, - @SerialName("card_old") val cardOld: String? = null, - - // 群头衔 - @SerialName("title") val title: String? = null, - - // 戳一戳 - @SerialName("poke_detail") val pokeDetail: PokeDetail? = null, - - // 群打卡 - @SerialName("sign_detail") val signDetail: SignDetail? = null, - @SerialName("source") val messageSource: MessageSource? = null, - - ) - -/** - * 不要使用继承的方式实现通用字段,那样会很难维护! - */ -@Serializable -internal data class RequestEvent( - @SerialName("time") val time: Long, - @SerialName("self_id") val selfId: Long, - @SerialName("post_type") val postType: PostType, - @SerialName("request_type") val type: RequestType, - @SerialName("sub_type") val subType: RequestSubType = RequestSubType.None, - @SerialName("group_id") val groupId: Long = -1, - @SerialName("user_id") val userId: Long = -1, - @SerialName("user_uid") val userUid: String = "", - @SerialName("comment") val comment: String = "", - @SerialName("flag") val flag: String? = null, -) - - -@Serializable -internal data class GroupFileMsg( - val id: String, - val name: String, - val size: Long, - val busid: Long, - val url: String, -) - -@Serializable -internal data class PrivateFileMsg( - val id: String, - val name: String, - val size: Long, - @SerialName("sub_id") val subId: String, - val url: String, - val expire: Long, -) - -@Serializable -internal data class PokeDetail( - val action: String? = "戳了戳", - val suffix: String? = "", - @SerialName("action_img_url") - val actionImg: String? = "https://tianquan.gtimg.cn/nudgeaction/item/0/expression.jpg", -) - -@Serializable -internal data class SignDetail( - val action: String? = "今日第1个打卡", - @SerialName("rank_img") - val rankImg: String? = "", -) diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/push/PushMetaEvent.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/push/PushMetaEvent.kt deleted file mode 100644 index 1fdd3a97..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/push/PushMetaEvent.kt +++ /dev/null @@ -1,32 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.service.data.push - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import moe.fuqiuluo.shamrock.remote.service.data.BotStatus - -@Serializable -internal enum class MetaEventType { - @SerialName("lifecycle") LifeCycle, - @SerialName("heartbeat") Heartbeat -} - -@Serializable -internal enum class MetaSubType { - @SerialName("enable") Enable, - @SerialName("disable") Disable, - @SerialName("connect") Connect, -} - -/** - * 不要使用继承的方式实现通用字段,那样会很难维护! - */ -@Serializable -internal data class PushMetaEvent( - @SerialName("time") val time: Long, - @SerialName("self_id") val selfId: Long, - @SerialName("post_type") val postType: PostType, - @SerialName("meta_event_type") val type: MetaEventType, - @SerialName("sub_type") val subType: MetaSubType, - @SerialName("status") val status: BotStatus, - @SerialName("interval") val interval: Long = 0, -) \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/AioListener.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/AioListener.kt deleted file mode 100644 index 864d5282..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/AioListener.kt +++ /dev/null @@ -1,582 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) - -package moe.fuqiuluo.shamrock.remote.service.listener - -import moe.fuqiuluo.shamrock.helper.MessageHelper -import com.tencent.qqnt.kernel.nativeinterface.* -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import moe.fuqiuluo.qqinterface.servlet.MsgSvc -import moe.fuqiuluo.qqinterface.servlet.TicketSvc -import moe.fuqiuluo.qqinterface.servlet.msg.MessageTempHandler -import moe.fuqiuluo.qqinterface.servlet.msg.toCQCode -import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc -import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.helper.db.MessageDB -import moe.fuqiuluo.shamrock.remote.service.api.GlobalEventTransmitter -import moe.fuqiuluo.qqinterface.servlet.transfile.RichMediaUploadHandler -import moe.fuqiuluo.shamrock.remote.service.data.push.MessageTempSource -import moe.fuqiuluo.shamrock.remote.service.data.push.PostType -import java.util.ArrayList -import java.util.Collections -import kotlin.collections.HashMap - -internal object AioListener : IKernelMsgListener { - override fun onRecvMsg(msgList: ArrayList) { - if (msgList.isEmpty()) return - - GlobalScope.launch { - msgList.forEach { - handleMsg(it) - } - } - } - - private suspend fun handleMsg(record: MsgRecord) { - try { - if (MessageTempHandler.notify(record)) return - if (record.msgSeq < 0) return - - val msgHash = MessageHelper.generateMsgIdHash(record.chatType, record.msgId) - - val peerId = when (record.chatType) { - MsgConstant.KCHATTYPEGUILD -> record.guildId - else -> record.peerUin.toString() - } - MessageHelper.saveMsgMapping( - hash = msgHash, - qqMsgId = record.msgId, - chatType = record.chatType, - subChatType = record.chatType, - peerId = peerId, - msgSeq = record.msgSeq.toInt(), - time = record.msgTime, - subPeerId = record.channelId ?: peerId - ) - - val rawMsg = record.elements.toCQCode(record.chatType, peerId, record.channelId ?: peerId) - if (rawMsg.isEmpty()) return - - if (ShamrockConfig.aliveReply() && rawMsg == "ping") { - MessageHelper.sendMessageWithoutMsgId(record.chatType, record.peerUin.toString(), "pong", { _, _ -> }) - } - - - val postType = if (record.senderUin == TicketSvc.getLongUin() && ShamrockConfig.enableSyncMsgAsSentMsg()) { - PostType.MsgSent - } else PostType.Msg - - //if (rawMsg.contains("forward")) { - // LogCenter.log(record.extInfoForUI.decodeToString(), Level.WARN) - //} - - when (record.chatType) { - MsgConstant.KCHATTYPEGROUP -> { - LogCenter.log("群消息(group = ${record.peerName}(${record.peerUin}), uin = ${record.senderUin}, id = $msgHash, seq = ${record.msgSeq}, msg = $rawMsg)") - ShamrockConfig.getGroupMsgRule()?.let { rule -> - if (!rule.black.isNullOrEmpty() && rule.black.contains(record.senderUin)) return - if (!rule.white.isNullOrEmpty() && !rule.white.contains(record.senderUin)) return - } - - if (!GlobalEventTransmitter.MessageTransmitter.transGroupMessage( - record, record.elements, rawMsg, msgHash, postType - ) - ) { - LogCenter.log("群消息推送失败 -> 推送目标可能不存在", Level.WARN) - } - } - - MsgConstant.KCHATTYPEC2C -> { - LogCenter.log("私聊消息(private = ${record.senderUin}, id = [$msgHash | ${record.msgId} | ${record.msgSeq}], msg = $rawMsg)") - ShamrockConfig.getPrivateRule()?.let { rule -> - if (!rule.black.isNullOrEmpty() && rule.black.contains(record.senderUin)) return - if (!rule.white.isNullOrEmpty() && !rule.white.contains(record.senderUin)) return - } - - if (!GlobalEventTransmitter.MessageTransmitter.transPrivateMessage( - record, record.elements, rawMsg, msgHash, postType - ) - ) { - LogCenter.log("私聊消息推送失败 -> MessageTransmitter", Level.WARN) - } - } - - MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> { - if (!ShamrockConfig.allowTempSession()) return - - ShamrockConfig.getPrivateRule()?.let { rule -> - if (!rule.black.isNullOrEmpty() && rule.black.contains(record.senderUin)) return - if (!rule.white.isNullOrEmpty() && !rule.white.contains(record.senderUin)) return - } - - var groupCode = 0L - var fromNick = "" - MsgSvc.getTempChatInfo(record.chatType, record.senderUid).onSuccess { - groupCode = it.groupCode.toLong() - fromNick = it.fromNick - } - - LogCenter.log("私聊临时消息(private = ${record.senderUin}, groupId=$groupCode, id = $msgHash, msg = $rawMsg)") - - if (!GlobalEventTransmitter.MessageTransmitter.transPrivateMessage( - record, - record.elements, - rawMsg, - msgHash, - tempSource = MessageTempSource.Group, - postType = postType, - groupId = groupCode, - fromNick = fromNick - ) - ) { - LogCenter.log("私聊临时消息推送失败 -> MessageTransmitter", Level.WARN) - } - } - - MsgConstant.KCHATTYPEGUILD -> { - LogCenter.log("频道消息(guildId = ${record.guildId}, sender = ${record.senderUid}, id = [$msgHash | ${record.msgId}], msg = $rawMsg)") - if (!GlobalEventTransmitter.MessageTransmitter - .transGuildMessage(record, record.elements, rawMsg, msgHash, postType = postType) - ) { - LogCenter.log("频道消息推送失败 -> MessageTransmitter", Level.WARN) - } - } - - else -> LogCenter.log("不支持PUSH事件: ${record.chatType}") - } - } catch (e: Throwable) { - LogCenter.log(e.stackTraceToString(), Level.WARN) - } - } - - override fun onMsgRecall(chatType: Int, peerId: String, msgId: Long) { - LogCenter.log("onMsgRecall($chatType, $peerId, $msgId)") - } - - override fun onAddSendMsg(record: MsgRecord) { - if (record.peerUin == TicketSvc.getLongUin()) return // 发给自己的消息不处理 - - GlobalScope.launch { - try { - val msgHash = MessageHelper.generateMsgIdHash(record.chatType, record.msgId) - val peerId = when (record.chatType) { - MsgConstant.KCHATTYPEGUILD -> record.guildId - else -> record.peerUin.toString() - } - MessageHelper.saveMsgMapping( - hash = msgHash, - qqMsgId = record.msgId, - chatType = record.chatType, - subChatType = record.chatType, - peerId = peerId, - msgSeq = record.msgSeq.toInt(), - time = record.msgTime, - subPeerId = record.channelId ?: peerId - ) - - LogCenter.log("预发送消息($msgHash | ${record.msgSeq} | ${record.msgId})") - } catch (e: Throwable) { - LogCenter.log(e.stackTraceToString(), Level.WARN) - } - } - } - - override fun onMsgInfoListUpdate(msgList: ArrayList?) { - msgList?.forEach { record -> - if (record.sendStatus == MsgConstant.KSENDSTATUSFAILED - || record.sendStatus == MsgConstant.KSENDSTATUSSENDING - ) { - return@forEach - } - - GlobalScope.launch { - val msgHash = MessageHelper.generateMsgIdHash(record.chatType, record.msgId) - val peerId = when (record.chatType) { - MsgConstant.KCHATTYPEGUILD -> record.guildId - else -> record.peerUin.toString() - } - - val mapping = MessageHelper.getMsgMappingByHash(msgHash) - if (mapping == null) { - MessageHelper.saveMsgMapping( - hash = msgHash, - qqMsgId = record.msgId, - chatType = record.chatType, - subChatType = record.chatType, - peerId = peerId, - msgSeq = record.msgSeq.toInt(), - time = record.msgTime, - subPeerId = record.channelId ?: peerId - ) - } else { - LogCenter.log("Update message info from ${mapping.msgSeq} to ${record.msgSeq}", Level.INFO) - MessageDB.getInstance().messageMappingDao() - .updateMsgSeqByMsgHash(msgHash, record.msgSeq.toInt()) - } - - if (!ShamrockConfig.enableSelfMsg() - || record.senderUin != TicketSvc.getLongUin() - || record.peerUin == TicketSvc.getLongUin() - ) return@launch - - val rawMsg = record.elements.toCQCode(record.chatType, peerId, record.channelId ?: peerId) - if (rawMsg.isEmpty()) return@launch - LogCenter.log("自发消息(target = ${record.peerUin}, id = $msgHash, msg = $rawMsg)") - - when (record.chatType) { - MsgConstant.KCHATTYPEGROUP -> { - if (!GlobalEventTransmitter.MessageTransmitter - .transGroupMessage(record, record.elements, rawMsg, msgHash, PostType.MsgSent) - ) { - LogCenter.log("自发群消息推送失败 -> MessageTransmitter", Level.WARN) - } - } - - MsgConstant.KCHATTYPEC2C -> { - if (!GlobalEventTransmitter.MessageTransmitter - .transPrivateMessage(record, record.elements, rawMsg, msgHash, PostType.MsgSent) - ) { - LogCenter.log("自发私聊消息推送失败 -> MessageTransmitter", Level.WARN) - } - } - - MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> { - if (!ShamrockConfig.allowTempSession()) return@launch - if (!GlobalEventTransmitter.MessageTransmitter - .transPrivateMessage( - record, - record.elements, - rawMsg, - msgHash, - PostType.MsgSent, - MessageTempSource.Group - ) - ) { - LogCenter.log("自发私聊临时消息推送失败 -> MessageTransmitter", Level.WARN) - } - } - - else -> LogCenter.log("不支持SELF PUSH事件: ${record.chatType}") - } - } - } - } - - override fun onTempChatInfoUpdate(tempChatInfo: TempChatInfo) { - - } - - override fun onMsgAbstractUpdate(arrayList: ArrayList?) { - //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?) { - LogCenter.log("onRecvS2CMsg(${arrayList.toString()})", Level.DEBUG) - } - - override fun onRecvSysMsg(arrayList: ArrayList?) { - 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>) { - // 推送未读消息数量 - } - - override fun onCustomWithdrawConfigUpdate(customWithdrawConfig: CustomWithdrawConfig?) { - LogCenter.log("onCustomWithdrawConfigUpdate: " + customWithdrawConfig.toString(), Level.DEBUG) - } - - override fun onDraftUpdate(contact: Contact?, arrayList: ArrayList?, 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 onFirstViewDirectMsgUpdate(firstViewDirectMsgNotifyInfo: FirstViewDirectMsgNotifyInfo?) { - - } - - override fun onFirstViewGroupGuildMapping(arrayList: ArrayList?) { - - } - - override fun onGrabPasswordRedBag( - i2: Int, - str: String?, - i3: Int, - recvdOrder: RecvdOrder?, - msgRecord: MsgRecord? - ) { - - } - - override fun onKickedOffLine(kickedInfo: KickedInfo?) { - LogCenter.log("onKickedOffLine($kickedInfo)") - } - - override fun onFileMsgCome(arrayList: ArrayList?) { - arrayList?.forEach { record -> - GlobalScope.launch { - when (record.chatType) { - MsgConstant.KCHATTYPEGROUP -> onGroupFileMsg(record) - MsgConstant.KCHATTYPEC2C -> onC2CFileMsg(record) - else -> LogCenter.log("不支持该来源的文件上传事件:${record}", Level.WARN) - } - } - } - } - - 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) - } - } - - 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) - } - } - - override fun onRichMediaUploadComplete(notifyInfo: FileTransNotifyInfo) { - LogCenter.log({ "[BDH] 资源上传完成(${notifyInfo.trasferStatus}, ${notifyInfo.fileId}, ${notifyInfo.msgId}, ${notifyInfo.commonFileInfo})" }, Level.DEBUG) - RichMediaUploadHandler.notify(notifyInfo) - } - - override fun onRecvOnlineFileMsg(arrayList: ArrayList?) { - 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?) { - //LogCenter.log("onLineDev($arrayList)") - } - - override fun onLogLevelChanged(newLevel: Long) { - - } - - override fun onMsgBoxChanged(arrayList: ArrayList?) { - - } - - override fun onMsgDelete(contact: Contact?, arrayList: ArrayList?) { - - } - - override fun onMsgEventListUpdate(hashMap: HashMap>?) { - - } - - override fun onMsgInfoListAdd(arrayList: ArrayList?) { - - } - - 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?) { - LogCenter.log("onSysMsgNotification($i2, $j2, $j3, $arrayList)", Level.DEBUG) - } - - override fun onUnreadCntAfterFirstView(hashMap: HashMap>?) { - - } - - override fun onUnreadCntUpdate(hashMap: HashMap>?) { - - } - - override fun onUserChannelTabStatusChanged(z: Boolean) { - - } - - override fun onUserOnlineStatusChanged(z: Boolean) { - - } - - override fun onUserTabStatusChanged(arrayList: ArrayList?) { - LogCenter.log("onUserTabStatusChanged($arrayList)", Level.DEBUG) - } - - override fun onlineStatusBigIconDownloadPush(i2: Int, j2: Long, str: String?) { - - } - - override fun onlineStatusSmallIconDownloadPush(i2: Int, j2: Long, str: String?) { - - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/GroupEventListener.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/GroupEventListener.kt deleted file mode 100644 index 9f9d7c04..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/GroupEventListener.kt +++ /dev/null @@ -1,136 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) - -package moe.fuqiuluo.shamrock.remote.service.listener - -import com.tencent.qqnt.kernel.nativeinterface.BulletinFeedsDownloadInfo -import com.tencent.qqnt.kernel.nativeinterface.DataSource -import com.tencent.qqnt.kernel.nativeinterface.GroupBulletin -import com.tencent.qqnt.kernel.nativeinterface.GroupBulletinListResult -import com.tencent.qqnt.kernel.nativeinterface.GroupDetailInfo -import com.tencent.qqnt.kernel.nativeinterface.GroupListUpdateType -import com.tencent.qqnt.kernel.nativeinterface.GroupMemberInfoListId -import com.tencent.qqnt.kernel.nativeinterface.GroupMemberListChangeInfo -import com.tencent.qqnt.kernel.nativeinterface.GroupMsgMaskInfo -import com.tencent.qqnt.kernel.nativeinterface.GroupNotifyMsg -import com.tencent.qqnt.kernel.nativeinterface.GroupSimpleInfo -import com.tencent.qqnt.kernel.nativeinterface.GroupStatisticInfo -import com.tencent.qqnt.kernel.nativeinterface.IKernelGroupListener -import com.tencent.qqnt.kernel.nativeinterface.JoinGroupNotifyMsg -import com.tencent.qqnt.kernel.nativeinterface.MemberInfo -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.shamrock.helper.LogCenter -import java.util.ArrayList -import java.util.HashMap - -internal object GroupEventListener: IKernelGroupListener { - override fun onGetGroupBulletinListResult( - j2: Long, - str: String?, - groupBulletinListResult: GroupBulletinListResult? - ) { - LogCenter.log("onGetGroupBulletinListResult($j2, $str, $groupBulletinListResult)") - } - - override fun onGroupAvatarUrlChange(j2: Long, str: String?) { - LogCenter.log("onGroupAvatarUrlChange($j2, $str)") - } - - override fun onGroupBulletinChange(j2: Long, groupBulletin: GroupBulletin?) { - LogCenter.log("onGroupBulletinChange($j2, $groupBulletin)") - } - - override fun onGroupBulletinRichMediaDownloadComplete(bulletinFeedsDownloadInfo: BulletinFeedsDownloadInfo?) { - LogCenter.log("onGroupBulletinRichMediaDownloadComplete($bulletinFeedsDownloadInfo)") - } - - override fun onGroupBulletinRichMediaProgressUpdate(bulletinFeedsDownloadInfo: BulletinFeedsDownloadInfo?) { - LogCenter.log("onGroupBulletinRichMediaProgressUpdate($bulletinFeedsDownloadInfo)") - } - - override fun onGroupConfMemberChange(j2: Long, arrayList: ArrayList?) { - LogCenter.log("onGroupConfMemberChange($j2, $arrayList)") - } - - override fun onGroupDetailInfoChange(groupDetailInfo: GroupDetailInfo?) { - LogCenter.log("onGroupDetailInfoChange($groupDetailInfo)") - } - - override fun onGroupListUpdate( - groupListUpdateType: GroupListUpdateType?, - arrayList: ArrayList? - ) { - LogCenter.log("onGroupListUpdate($groupListUpdateType, $arrayList)") - } - - override fun onGroupNotifiesUnreadCountUpdated(z: Boolean, j2: Long, i2: Int) { - LogCenter.log("onGroupNotifiesUnreadCountUpdated($z, $j2, $i2)") - } - - override fun onGroupNotifiesUpdated(z: Boolean, arrayList: ArrayList?) { - LogCenter.log("onGroupNotifiesUpdated($z, $arrayList)") - } - - override fun onGroupPortraitChange( - j2: Long, - arrayList: ArrayList?, - arrayList2: ArrayList? - ) { - LogCenter.log("onGroupPortraitChange($j2, $arrayList, $arrayList2)") - } - - override fun onGroupSingleScreenNotifies( - z: Boolean, - j2: Long, - arrayList: ArrayList? - ) { - LogCenter.log("onGroupSingleScreenNotifies($z, $j2, $arrayList)") - } - - override fun onGroupStatisticInfoChange(j2: Long, groupStatisticInfo: GroupStatisticInfo?) { - LogCenter.log("onGroupStatisticInfoChange($j2, $groupStatisticInfo)") - } - - override fun onGroupsMsgMaskResult(arrayList: ArrayList?) { - LogCenter.log("onGroupsMsgMaskResult($arrayList)") - } - - override fun onJoinGroupNotify(joinGroupNotifyMsg: JoinGroupNotifyMsg?) { - LogCenter.log("onJoinGroupNotify($joinGroupNotifyMsg)") - } - - override fun onMemberInfoChange( - groupCode: Long, - dataSource: DataSource, - hashMap: HashMap - ) { - /*GlobalScope.launch { - hashMap.values.forEach { memberInfo -> - GroupSvc.getTroopMemberInfoByUid(groupCode, memberInfo.uid).onSuccess { - LogCenter.log("onMemberInfoChange($groupCode, $dataSource, $it, $memberInfo)") - }.onFailure { - LogCenter.log("onMemberInfoChange($groupCode, $dataSource, $it, $memberInfo)") - } - } - }*/ - } - - override fun onMemberListChange(groupMemberListChangeInfo: GroupMemberListChangeInfo?) { - LogCenter.log("onMemberListChange($groupMemberListChangeInfo)") - } - - override fun onSearchMemberChange( - str: String?, - str2: String?, - arrayList: ArrayList?, - hashMap: HashMap? - ) { - LogCenter.log("onSearchMemberChange($str, $str2, $arrayList, $hashMap)") - } - - override fun onShutUpMemberListChanged(j2: Long, arrayList: ArrayList?) { - LogCenter.log("onShutUpMemberListChanged($j2, $arrayList)") - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/KernelGuildListener.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/KernelGuildListener.kt deleted file mode 100644 index 5785004b..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/KernelGuildListener.kt +++ /dev/null @@ -1,753 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.service.listener - -import com.tencent.qqnt.kernel.nativeinterface.GProAVChannelConfig -import com.tencent.qqnt.kernel.nativeinterface.GProAVRoomOptPushInfo -import com.tencent.qqnt.kernel.nativeinterface.GProAVUserStateChangeInfo -import com.tencent.qqnt.kernel.nativeinterface.GProCategoryChannelIdList -import com.tencent.qqnt.kernel.nativeinterface.GProChannel -import com.tencent.qqnt.kernel.nativeinterface.GProChannelState -import com.tencent.qqnt.kernel.nativeinterface.GProChannelUserChangeInfo -import com.tencent.qqnt.kernel.nativeinterface.GProClientIdentity -import com.tencent.qqnt.kernel.nativeinterface.GProCreateGuildGuideInfo -import com.tencent.qqnt.kernel.nativeinterface.GProDailyRecommendPush -import com.tencent.qqnt.kernel.nativeinterface.GProDiscoveryStateChangedMsg -import com.tencent.qqnt.kernel.nativeinterface.GProGlobalBanner -import com.tencent.qqnt.kernel.nativeinterface.GProGuild -import com.tencent.qqnt.kernel.nativeinterface.GProGuildData -import com.tencent.qqnt.kernel.nativeinterface.GProGuildInit -import com.tencent.qqnt.kernel.nativeinterface.GProGuildListSortInfo -import com.tencent.qqnt.kernel.nativeinterface.GProGuildMemberCountInfo -import com.tencent.qqnt.kernel.nativeinterface.GProGuildRole -import com.tencent.qqnt.kernel.nativeinterface.GProGuildSpeakableThreshold -import com.tencent.qqnt.kernel.nativeinterface.GProGuildStateRspInfo -import com.tencent.qqnt.kernel.nativeinterface.GProGuildUserProfile -import com.tencent.qqnt.kernel.nativeinterface.GProHeartbeatRsq -import com.tencent.qqnt.kernel.nativeinterface.GProLiveRoomInfo -import com.tencent.qqnt.kernel.nativeinterface.GProMedal -import com.tencent.qqnt.kernel.nativeinterface.GProMember -import com.tencent.qqnt.kernel.nativeinterface.GProNoticeRedPoint -import com.tencent.qqnt.kernel.nativeinterface.GProPollingChannelState -import com.tencent.qqnt.kernel.nativeinterface.GProPollingData -import com.tencent.qqnt.kernel.nativeinterface.GProPreventAddictionPushInfo -import com.tencent.qqnt.kernel.nativeinterface.GProProgramInfo -import com.tencent.qqnt.kernel.nativeinterface.GProQQMsgListChannel -import com.tencent.qqnt.kernel.nativeinterface.GProQQMsgListGuild -import com.tencent.qqnt.kernel.nativeinterface.GProRecommendGuildInfo -import com.tencent.qqnt.kernel.nativeinterface.GProRecommendGuildPersonalSetting -import com.tencent.qqnt.kernel.nativeinterface.GProRoleMemberChangeInfo -import com.tencent.qqnt.kernel.nativeinterface.GProSchemeConfig -import com.tencent.qqnt.kernel.nativeinterface.GProScreenShareUser -import com.tencent.qqnt.kernel.nativeinterface.GProSecurityResult -import com.tencent.qqnt.kernel.nativeinterface.GProSendGiftEventData -import com.tencent.qqnt.kernel.nativeinterface.GProStickyChannel -import com.tencent.qqnt.kernel.nativeinterface.GProTopMsg -import com.tencent.qqnt.kernel.nativeinterface.GProUser -import com.tencent.qqnt.kernel.nativeinterface.GProUserChannelState -import com.tencent.qqnt.kernel.nativeinterface.GProUserGiftRankInfo -import com.tencent.qqnt.kernel.nativeinterface.GProVoiceSmobaGameRoomManageSysMsg -import com.tencent.qqnt.kernel.nativeinterface.GProVoiceSmobaGameUserActionPush -import com.tencent.qqnt.kernel.nativeinterface.GProWorldState -import com.tencent.qqnt.kernel.nativeinterface.GProYLGameTeamInfo -import com.tencent.qqnt.kernel.nativeinterface.IKernelGuildListener -import moe.fuqiuluo.shamrock.helper.LogCenter -import java.util.ArrayList -import java.util.HashMap - -internal object KernelGuildListener: IKernelGuildListener { - override fun onAVChannelThemeUpdate( - j2: Long, - j3: Long, - gProAVChannelConfig: GProAVChannelConfig? - ) { - - } - - override fun onAVUserInfoChangeNotifyForAll(gProChannelUserChangeInfo: GProChannelUserChangeInfo?) { - - } - - override fun onAllGuildChannelListFetchCompleted() { - - } - - override fun onAnchorStatusChange(j2: Long, j3: Long, i2: Int) { - - } - - override fun onAppChannelPreInfosUpdated( - j2: Long, - arrayList: ArrayList?, - arrayList2: ArrayList? - ) { - - } - - override fun onAppInfosUpdated() { - - } - - override fun onBatchChannelListUpdated(arrayList: ArrayList?) { - - } - - override fun onBatchFetchRoleListUpdate(j2: Long, arrayList: ArrayList?) { - - } - - override fun onBroadcastRoomClose(j2: Long, i2: Int, str: String?) { - - } - - override fun onBroadcastUserCountUpdate(j2: Long, i2: Int) { - - } - - override fun onChangeGuildNumber(j2: Long, str: String?, i2: Int) { - - } - - override fun onChannelInfoUpdated(i2: Int, str: String?, gProChannel: GProChannel?) { - - } - - override fun onChannelListUpdated( - i2: Int, - str: String?, - j2: Long, - i3: Int, - hashMap: HashMap?, - gProCategoryChannelIdList: GProCategoryChannelIdList?, - arrayList: ArrayList? - ) { - - } - - override fun onChannelUserPermissionChange(j2: Long, arrayList: ArrayList?) { - - } - - override fun onDiscoveryStateChanged(gProDiscoveryStateChangedMsg: GProDiscoveryStateChangedMsg?) { - - } - - override fun onEnterSpeakQueueNotifyForAll(gProChannelUserChangeInfo: GProChannelUserChangeInfo?) { - - } - - override fun onGetSelfTinyId(j2: Long) { - - } - - override fun onGlobalBannerRemoved(gProGlobalBanner: GProGlobalBanner?) { - - } - - override fun onGlobalBannerUpdated(gProGlobalBanner: GProGlobalBanner?) { - - } - - override fun onGuildCreatorGuideUpdated(gProCreateGuildGuideInfo: GProCreateGuildGuideInfo?) { - - } - - override fun onGuildInfoUpdated(i2: Int, str: String?, gProGuild: GProGuild?, z: Boolean) { - - } - - override fun onGuildListUpdated( - retcode: Int, - why: String?, - force: Boolean, - guildIdList: ArrayList?, - guildList: ArrayList?, - sortInfo: GProGuildListSortInfo? - ) { - - } - - override fun onGuildUserAvatarMetasUpdated(j2: Long, arrayList: ArrayList?) { - - } - - override fun onGuildUserAvatarPendantsUpdated(hashMap: HashMap?) { - - } - - override fun onGuildUserChannelCategoryAdminListUpdated(j2: Long, arrayList: ArrayList?) { - - } - - override fun onGuildUserClientIdentitiesUpdated(j2: Long, arrayList: ArrayList?) { - - } - - override fun onGuildUserLevelRolesChanged(j2: Long, hashMap: HashMap?) { - - } - - override fun onGuildUserMedalsUpdated(hashMap: HashMap?) { - - } - - override fun onGuildUserMemberNamesUpdated(j2: Long, hashMap: HashMap?) { - - } - - override fun onGuildUserNicknamesUpdated(hashMap: HashMap?) { - - } - - override fun onGuildUserTopRolesChanged(j2: Long, arrayList: ArrayList?) { - - } - - override fun onLocalMemberCountUpdate(j2: Long, j3: Long) { - - } - - override fun onMemberCountUpdate(gProGuildMemberCountInfo: GProGuildMemberCountInfo?) { - - } - - override fun onNativeUpdateSimpleProfileByMsg( - j2: Long, - j3: Long, - j4: Long, - j5: Long, - j6: Long, - j7: Long, - i2: Int, - str: String?, - str2: String?, - str3: String?, - gProClientIdentity: GProClientIdentity?, - j8: Long, - str4: String?, - gProMedal: GProMedal? - ) { - - } - - override fun onNoticeListUpdate(gProNoticeRedPoint: GProNoticeRedPoint?, bArr: ByteArray?) { - - } - - override fun onOpenTelemetryMetricCountReport( - str: String?, - hashMap: HashMap?, - z: Boolean - ) { - - } - - override fun onOpenTelemetryMetricTimeCostReport( - str: String?, - hashMap: HashMap?, - j2: Long - ) { - - } - - override fun onOpenTelemetryTraceReport(str: String?, hashMap: HashMap?) { - - } - - override fun onPollingResult(arrayList: ArrayList?) { - - } - - override fun onPollingYLGameTeamInfo(gProYLGameTeamInfo: GProYLGameTeamInfo?) { - - } - - override fun onPushAVChannelAppMsg(str: String?) { - - } - - override fun onPushAVChannelConfigUpdate( - j2: Long, - j3: Long, - gProAVChannelConfig: GProAVChannelConfig? - ) { - - } - - override fun onPushAVChannelPlayListChange( - j2: Long, - j3: Long, - str: String?, - i2: Int, - str2: String? - ) { - - } - - override fun onPushAVHeartbeatRsp(i2: Int, str: String?, gProHeartbeatRsq: GProHeartbeatRsq?) { - - } - - override fun onPushAVRoomOptChange(gProAVRoomOptPushInfo: GProAVRoomOptPushInfo?) { - - } - - override fun onPushAVUserStateChange(gProAVUserStateChangeInfo: GProAVUserStateChangeInfo?) { - - } - - override fun onPushAddChannelSpeakPermission(j2: Long, j3: Long) { - - } - - override fun onPushAdminChanged(j2: Long, z: Boolean, j3: Long, j4: Long) { - - } - - override fun onPushAllowScreenShareInGuild(j2: Long, j3: Long, z: Boolean, j4: Long) { - - } - - override fun onPushAudioChannelUserEnter(j2: Long, j3: Long, gProUser: GProUser?, j4: Long) { - - } - - override fun onPushAudioChannelUserExit(j2: Long, j3: Long, j4: Long, j5: Long) { - - } - - override fun onPushAudioChannelUserPlatSwitch( - j2: Long, - j3: Long, - j4: Long, - i2: Int, - i3: Int, - str: String?, - j5: Long - ) { - - } - - override fun onPushBannedStatusChanged( - j2: Long, - z: Boolean, - z2: Boolean, - z3: Boolean, - j3: Long - ) { - - } - - override fun onPushBatchJoinChannel(j2: Long, arrayList: ArrayList?) { - - } - - override fun onPushBatchLeaveChannel(j2: Long, arrayList: ArrayList?) { - - } - - override fun onPushBussinessConfig(i2: Int, bArr: ByteArray?) { - - } - - override fun onPushCanceledSpeak(gProAVUserStateChangeInfo: GProAVUserStateChangeInfo?) { - - } - - override fun onPushChangeRoleMember(j2: Long, arrayList: ArrayList?) { - - } - - override fun onPushChannelAdminChange( - j2: Long, - j3: Long, - i2: Int, - arrayList: ArrayList? - ) { - - } - - override fun onPushChannelCategoryChanged(gProGuildInit: GProGuildInit?) { - - } - - override fun onPushChannelCreated(j2: Long, j3: Long, gProGuildInit: GProGuildInit?) { - - } - - override fun onPushChannelDestroy(j2: Long, arrayList: ArrayList?, j3: Long) { - - } - - override fun onPushChannelStateChange(arrayList: ArrayList?) { - - } - - override fun onPushChannelTopMsgUpdated( - j2: Long, - j3: Long, - j4: Long, - arrayList: ArrayList?, - arrayList2: ArrayList? - ) { - - } - - override fun onPushChannelVisibleChanged(j2: Long, j3: Long, i2: Int, i3: Int) { - - } - - override fun onPushCreateGuild( - j2: Long, - gProGuild: GProGuild?, - hashMap: HashMap?, - gProCategoryChannelIdList: GProCategoryChannelIdList?, - arrayList: ArrayList? - ) { - - } - - override fun onPushCreateRole(j2: Long, arrayList: ArrayList?) { - - } - - override fun onPushDeleteRole(j2: Long, arrayList: ArrayList?) { - - } - - override fun onPushDestroyGuild(j2: Long) { - - } - - override fun onPushDirectMsgSwitchUpdate(i2: Int) { - - } - - override fun onPushGuildPermissionChanged(j2: Long, arrayList: ArrayList?) { - - } - - override fun onPushGuildStateChange( - i2: Int, - str: String?, - gProGuildStateRspInfo: GProGuildStateRspInfo? - ) { - - } - - override fun onPushInvitationRefused(j2: Long, j3: Long, j4: Long, str: String?) { - - } - - override fun onPushInviteMemberEvent( - j2: Long, - j3: Long, - j4: Long, - gProSchemeConfig: GProSchemeConfig? - ) { - - } - - override fun onPushInvitedToSpeak(gProAVUserStateChangeInfo: GProAVUserStateChangeInfo?) { - - } - - override fun onPushJoinChannel(j2: Long, j3: Long, arrayList: ArrayList?) { - - } - - override fun onPushJoinGuild( - j2: Long, - j3: Long, - j4: Long, - i2: Int, - gProGuild: GProGuild?, - hashMap: HashMap?, - gProCategoryChannelIdList: GProCategoryChannelIdList?, - arrayList: ArrayList?, - i3: Int - ) { - - } - - override fun onPushJoinGuildFail(j2: Long, j3: Long, i2: Int, i3: Int, str: String?) { - - } - - override fun onPushKickOffGuild(j2: Long, j3: Long, j4: Long, i2: Int) { - - } - - override fun onPushKickOutAudioChannel( - j2: Long, - j3: Long, - j4: Long, - j5: Long, - i2: Int, - str: String?, - i3: Int, - j6: Long - ) { - - } - - override fun onPushLeaveChannel(j2: Long, j3: Long, arrayList: ArrayList?) { - - } - - override fun onPushLiveChannelAnchorIdentityChange(j2: Long, j3: Long, i2: Int) { - - } - - override fun onPushLiveRoomInfoChange(j2: Long, j3: Long, str: String?, str2: String?) { - - } - - override fun onPushLiveRoomStatusChangeMsg( - j2: Long, - j3: Long, - j4: Long, - str: String?, - j5: Long, - i2: Int, - i3: Int, - gProProgramInfo: GProProgramInfo?, - j6: Long, - gProLiveRoomInfo: GProLiveRoomInfo? - ) { - - } - - override fun onPushMemberTopRoleChanged(j2: Long, j3: Long, gProGuildRole: GProGuildRole?) { - - } - - override fun onPushMemberTopRoleInChannelChanged( - j2: Long, - j3: Long, - j4: Long, - gProGuildRole: GProGuildRole? - ) { - - } - - override fun onPushModifyRole(j2: Long, j3: Long, gProGuildRole: GProGuildRole?) { - - } - - override fun onPushMsgRecvTypeChanged(j2: Long, j3: Long, j4: Long, i2: Int) { - - } - - override fun onPushNotifySwitchUpdate(j2: Long, j3: Long, i2: Int) { - - } - - override fun onPushPermissionInGuild(j2: Long, j3: Long, z: Boolean, j4: Long) { - - } - - override fun onPushPreventAddictionInstructions(gProPreventAddictionPushInfo: GProPreventAddictionPushInfo?) { - - } - - override fun onPushQuitGuild(j2: Long, j3: Long) { - - } - - override fun onPushRemoveChannelSpeakPermission(j2: Long, j3: Long) { - - } - - override fun onPushSecurityResult(gProSecurityResult: GProSecurityResult?) { - - } - - override fun onPushSelfBannedSpeakChange(j2: Long, j3: Long, j4: Long, i2: Int) { - - } - - override fun onPushSendGiftEventNotify(gProSendGiftEventData: GProSendGiftEventData?) { - - } - - override fun onPushShutUpStateChanged(j2: Long, j3: Long) { - - } - - override fun onPushSortRole(j2: Long) { - - } - - override fun onPushSwitchLiveRoom( - j2: Long, - j3: Long, - arrayList: ArrayList?, - i2: Int - ) { - - } - - override fun onPushUserChannelStateChange(gProUserChannelState: GProUserChannelState?) { - - } - - override fun onPushUserGiftRankChangeNotify(gProUserGiftRankInfo: GProUserGiftRankInfo?) { - - } - - override fun onPushUserHandUpResult(gProAVUserStateChangeInfo: GProAVUserStateChangeInfo?) { - - } - - override fun onPushUserMuteSeatInGuild(j2: Long, j3: Long, z: Boolean, j4: Long) { - - } - - override fun onPushUserScreenShare( - j2: Long, - j3: Long, - arrayList: ArrayList?, - bArr: ByteArray? - ) { - - } - - override fun onPushWorldStateChange(gProWorldState: GProWorldState?) { - - } - - override fun onQQMsgListChannelDataReady() { - - } - - override fun onQQMsgListChannelUpdated( - arrayList: ArrayList?, - arrayList2: ArrayList? - ) { - - } - - override fun onQQMsgListGuildUpdated( - arrayList: ArrayList?, - arrayList2: ArrayList? - ) { - - } - - override fun onRecommendGuildChannelListUpdate(gProGuildData: GProGuildData?) { - - } - - override fun onRecommendGuildEntryUpdateNotify() { - - } - - override fun onRecommendGuildInfoUpdate(gProRecommendGuildInfo: GProRecommendGuildInfo?) { - - } - - override fun onRecommendGuildJumpChannelNotify(gProDailyRecommendPush: GProDailyRecommendPush?) { - - } - - override fun onRecommendGuildPersonalSettingUpdate(gProRecommendGuildPersonalSetting: GProRecommendGuildPersonalSetting?) { - - } - - override fun onRecommendGuildPollingResult(arrayList: ArrayList?) { - - } - - override fun onRecommendGuildStickyTopUpdated( - arrayList: ArrayList?, - arrayList2: ArrayList?, - arrayList3: ArrayList? - ) { - - } - - override fun onRefreshGuildUserProfileInfo( - i2: Int, - str: String?, - j2: Long, - j3: Long, - gProGuildUserProfile: GProGuildUserProfile? - ) { - - } - - override fun onReportSqliteError(i2: Int, str: String?) { - - } - - override fun onRobotStateChangeNotifyForAll(gProChannelUserChangeInfo: GProChannelUserChangeInfo?) { - - } - - override fun onSessionInitComplete(i2: Int, j2: Long) { - - } - - override fun onSmobaGameUserChangeNotifyForAll(gProChannelUserChangeInfo: GProChannelUserChangeInfo?) { - - } - - override fun onSpeakableThresholdUpdate( - arrayList: ArrayList?, - arrayList2: ArrayList? - ) { - - } - - override fun onStickyChannelUpdated( - j2: Long, - arrayList: ArrayList?, - arrayList2: ArrayList?, - arrayList3: ArrayList? - ) { - - } - - override fun onTabRedPointPollingResult(z: Boolean, str: String?) { - - } - - override fun onUserAVStateResetNotifyForAll(gProChannelUserChangeInfo: GProChannelUserChangeInfo?) { - - } - - override fun onUserEnterListNotifyForAll(gProChannelUserChangeInfo: GProChannelUserChangeInfo?) { - - } - - override fun onUserHandUpRequestNotifyForAll(gProChannelUserChangeInfo: GProChannelUserChangeInfo?) { - - } - - override fun onUserLeaveListNotifyForAll(gProChannelUserChangeInfo: GProChannelUserChangeInfo?) { - - } - - override fun onUserListFetchFinish(i2: Int, str: String?, j2: Long) { - - } - - override fun onUserSpeakingNotifyForAll(gProChannelUserChangeInfo: GProChannelUserChangeInfo?) { - - } - - override fun onUserWaitingToSpeakNotifyForAll(gProChannelUserChangeInfo: GProChannelUserChangeInfo?) { - - } - - override fun onVoiceHeartbeatTimeout(j2: Long, j3: Long) { - - } - - override fun onVoiceSmobaGameRooManageUpdate(gProVoiceSmobaGameRoomManageSysMsg: GProVoiceSmobaGameRoomManageSysMsg?) { - - } - - override fun onVoiceSmobaGameUserActionUpdate(gProVoiceSmobaGameUserActionPush: GProVoiceSmobaGameUserActionPush?) { - - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/NetworkListener.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/NetworkListener.kt deleted file mode 100644 index cc917910..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/NetworkListener.kt +++ /dev/null @@ -1,11 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.service.listener - -import com.tencent.qqnt.kernel.nativeinterface.IQQNTWrapperNetworkListener -import com.tencent.qqnt.kernel.nativeinterface.NetStatusType -import moe.fuqiuluo.shamrock.helper.LogCenter - -internal object NetworkListener: IQQNTWrapperNetworkListener { - override fun onNetworkStatusChanged(o: NetStatusType, n: NetStatusType) { - LogCenter.log("网络波动: $o -> $n") - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/PrimitiveListener.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/PrimitiveListener.kt deleted file mode 100644 index 1c5d4bbb..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/PrimitiveListener.kt +++ /dev/null @@ -1,625 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.service.listener - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -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.qqinterface.servlet.FriendSvc.requestFriendSystemMsgNew -import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.qqinterface.servlet.GroupSvc.requestGroupSystemMsgNew -import moe.fuqiuluo.qqinterface.servlet.TicketSvc.getLongUin -import moe.fuqiuluo.shamrock.helper.ContactHelper -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.remote.service.api.GlobalEventTransmitter -import moe.fuqiuluo.shamrock.remote.service.data.push.NoticeSubType -import moe.fuqiuluo.shamrock.remote.service.data.push.NoticeType -import moe.fuqiuluo.shamrock.remote.service.data.push.RequestSubType -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.shamrock.xposed.helper.PacketHandler -import moe.fuqiuluo.symbols.decodeProtobuf -import protobuf.message.ContentHead -import protobuf.message.MsgBody -import protobuf.message.ResponseHead -import protobuf.push.* - -internal object PrimitiveListener { - fun registerListener() { - PacketHandler.register("trpc.msg.olpush.OlPushService.MsgPush") { _, buffer -> - GlobalScope.launch { - try { - val push = buffer.slice(4).decodeProtobuf() - 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) { - runCatching { - - } - } - - private suspend fun onC2CPoke(msgTime: Long, body: MsgBody) { - val event = body.msgContent!!.decodeProtobuf() - 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() - 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() -// }.getOrElse { -// val readPacket = ByteReadPacket(body.msgContent!!) -// readPacket.readBuf32Long() -// readPacket.discardExact(1) -// -// readPacket.readBytes(readPacket.readShort().toInt()).also { -// readPacket.release() -// }.decodeProtobuf() -// } -// -// 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() - }.getOrElse { - val readPacket = ByteReadPacket(body.msgContent!!) - readPacket.readBuf32Long() - readPacket.discardExact(1) - - readPacket.readBytes(readPacket.readShort().toInt()).also { - readPacket.release() - }.decodeProtobuf() - } - 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(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() - }.getOrElse { - val readPacket = ByteReadPacket(body.msgContent!!) - readPacket.readBuf32Long() - readPacket.discardExact(1) - - readPacket.readBytes(readPacket.readShort().toInt()).also { - readPacket.release() - }.decodeProtobuf() - } - val groupId = event.groupCode.toLong() - val detail = event.essenceMsgInfo!!.first() - - val megSeq = event.msgSeq.toInt() - val senderUin = detail.sender.toLong() - val operatorUin = detail.operator.toLong() - val msgHashId = MessageHelper.getMsgMappingBySeq(MsgConstant.KCHATTYPEGROUP, groupId.toString(), megSeq).also { - if (it == null) { - LogCenter.log("精华消息变动推送失败!找不到消息映射关系!", Level.WARN) - return - } - }!!.msgHashId - - val subType = when (val type = detail.type) { - 1u -> { - LogCenter.log("群设精消息(groupId=$groupId, sender=$senderUin, msgId=$msgHashId, operator=$operatorUin)") - NoticeSubType.Add - } - - 2u -> { - LogCenter.log("群撤精消息(groupId=$groupId, sender=$senderUin, msgId=$msgHashId, operator=$operatorUin)") - NoticeSubType.Delete - } - - else -> error("onEssenceMessage unknown type: $type") - } - - if (!GlobalEventTransmitter.GroupNoticeTransmitter - .transEssenceChange(msgTime, senderUin, operatorUin, msgHashId, groupId, subType) - ) { - LogCenter.log("精华消息变动推送失败!", Level.WARN) - } - } - - - private suspend fun onGroupCommonTips(time: Long, body: MsgBody) { - val event = runCatching { - body.msgContent!!.decodeProtobuf() - }.getOrElse { - val readPacket = ByteReadPacket(body.msgContent!!) - readPacket.discardExact(4) - readPacket.discardExact(1) - - readPacket.readBytes(readPacket.readShort().toInt()).also { - readPacket.release() - }.decodeProtobuf() - } - 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() - val head = event.head!! - - val operationUid = head.operator!! - val operator = ContactHelper.getUinByUidAsync(operationUid).toLong() - - val msgSeq = head.msgSeq - val tipText = head.wording?.wording ?: "" - - val mapping = MessageHelper.getMsgMappingBySeq(MsgConstant.KCHATTYPEC2C, operator.toString(), msgSeq.toInt()) - if (mapping == null) { - LogCenter.log("由于缺失消息映射关系,消息撤回事件无法推送!", Level.WARN) - return - } - - LogCenter.log("私聊消息撤回: $operator, seq = $msgSeq, hash = ${mapping.msgHashId}, tip = $tipText") - - if (!GlobalEventTransmitter.PrivateNoticeTransmitter - .transPrivateRecall(time, operator, mapping.msgHashId, tipText) - ) { - LogCenter.log("私聊消息撤回推送失败!", Level.WARN) - } - } - - private suspend fun onGroupMemIncreased(time: Long, body: MsgBody) { - val event = body.msgContent!!.decodeProtobuf() - val groupCode = event.groupCode - val targetUid = event.memberUid - val type = event.type - - GroupSvc.getGroupMemberList(groupCode, 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 - .transGroupMemberNumChanged( - time, - target, - targetUid, - groupCode, - operator, - operatorUid, - NoticeType.GroupMemIncrease, - when (type) { - 130 -> NoticeSubType.Approve - 131 -> NoticeSubType.Invite - else -> NoticeSubType.Approve - } - ) - ) { - LogCenter.log("群成员增加推送失败!", Level.WARN) - } - } - - private suspend fun onGroupMemberDecreased(time: Long, body: MsgBody) { - val event = body.msgContent!!.decodeProtobuf() - val groupCode = event.groupCode - val targetUid = event.memberUid - val type = event.type - val operatorUid = event.operatorUid - - GroupSvc.getGroupMemberList(groupCode, 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 -> NoticeSubType.Leave - 131 -> NoticeSubType.Kick - 3 -> NoticeSubType.KickMe - else -> { - NoticeSubType.Kick - } - } - - LogCenter.log("群成员减少($groupCode): $target, type = $subtype ($type)") - - if (!GlobalEventTransmitter.GroupNoticeTransmitter - .transGroupMemberNumChanged( - time, - target, - targetUid, - groupCode, - operator, - operatorUid, - NoticeType.GroupMemDecrease, - subtype - ) - ) { - LogCenter.log("群成员减少推送失败!", Level.WARN) - } - } - - private suspend fun onGroupAdminChange(msgTime: Long, body: MsgBody) { - val event = body.msgContent!!.decodeProtobuf() - 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() - 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() - val subType = if (rawDuration == 0) NoticeSubType.LiftBan else NoticeSubType.Ban - - if (wholeBan) { - LogCenter.log("群全员禁言($groupCode): $operator -> ${if (subType == NoticeSubType.Ban) "开启" else "关闭"}") - } else { - LogCenter.log("群禁言($groupCode): $operator -> $target, 时长 = ${duration}s") - } - if (!GlobalEventTransmitter.GroupNoticeTransmitter - .transGroupBan(msgTime, subType, operator, operatorUid, target, targetUid, groupCode, duration) - ) { - LogCenter.log("群禁言推送失败!", Level.WARN) - } - } - - private suspend fun onGroupRecall(time: Long, body: MsgBody) { - val event = runCatching { - body.msgContent!!.decodeProtobuf() - }.getOrElse { - val readPacket = ByteReadPacket(body.msgContent!!) - readPacket.discardExact(4) - readPacket.discardExact(1) - readPacket.readBytes(readPacket.readShort().toInt()).also { - readPacket.release() - }.decodeProtobuf() - } - 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 mapping = MessageHelper.getMsgMappingBySeq(MsgConstant.KCHATTYPEGROUP, groupCode.toString(), msgSeq.toInt()) - if (mapping == null) { - LogCenter.log("由于缺失消息映射关系(seq = $msgSeq),消息撤回事件无法推送!", Level.WARN) - return - } - val msgHash = mapping.msgHashId - val operator = ContactHelper.getUinByUidAsync(operatorUid).toLong() - val target = ContactHelper.getUinByUidAsync(targetUid).toLong() - LogCenter.log("群消息撤回($groupCode): $operator -> $target, seq = $msgSeq, hash = $msgHash, tip = $tipText") - - if (!GlobalEventTransmitter.GroupNoticeTransmitter - .transGroupMsgRecall(time, operator, target, groupCode, msgHash, tipText) - ) { - LogCenter.log("群消息撤回推送失败!", Level.WARN) - } - } - - private suspend fun onGroupApply(time: Long, contentHead: ContentHead, body: MsgBody) { - when (contentHead.msgType) { - 84 -> { - val event = body.msgContent!!.decodeProtobuf() - val groupCode = event.groupCode - val applierUid = event.applierUid - val reason = event.applyMsg ?: "" - var applier = ContactHelper.getUinByUidAsync(applierUid).toLong() - if (applier == getLongUin()) { - 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, RequestSubType.Add) - ) { - LogCenter.log("入群申请推送失败!", Level.WARN) - } - } - - 528 -> { - val event = body.msgContent!!.decodeProtobuf() - val groupCode = event.applyInfo?.groupCode ?: return - val applierUid = event.applyInfo?.applierUid ?: return - var applier = ContactHelper.getUinByUidAsync(applierUid).toLong() - if (applier == getLongUin()) { - 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, RequestSubType.Add) - ) { - LogCenter.log("邀请入群申请推送失败!", Level.WARN) - } - } - } - } - - private suspend fun onInviteGroup(time: Long, msgHead: ResponseHead, body: MsgBody) { - val event = body.msgContent!!.decodeProtobuf() - 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, RequestSubType.Invite) - ) { - LogCenter.log("邀请入群推送失败!", Level.WARN) - } - } - -} diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/structures/CommonResult.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/structures/CommonResult.kt deleted file mode 100644 index 89ece553..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/structures/CommonResult.kt +++ /dev/null @@ -1,56 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.structures - -import kotlinx.serialization.Contextual -import kotlinx.serialization.Serializable -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonElement - -enum class Status( - val code: Int -) { - Ok(0), - BadRequest(10001), - ErrorToken(403), - UnsupportedAction(10002), - BadParam(10003), - UnsupportedParam(10004), - UnsupportedSegment(10005), - UnsupportedSegmentData(10007), - BadSegmentData(10006), - WhoAmI(10101), - UnknownSelf(10102), - BadHandler(20001), - InternalHandlerError(20002), - DatabaseError(31000), - FilesystemError(32000), - NetworkError(33000), - PlatformError(34000), - LogicError(35000), - IAmTired(36000), -} - -@Serializable -data class CommonResult( - var status: String, - var retcode: Int, - @Contextual - var data: T, - var message: String = "", - var echo: JsonElement? = null -) - -@Serializable -object EmptyObject - -internal inline fun resultToString( - isOk: Boolean, - code: Status, - data: T, - msg: String = "", - echo: JsonElement -): String { - return Json.encodeToString( - CommonResult(if (isOk) "ok" else "failed", code.code, data, msg, echo) - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/structures/CurrentAccount.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/structures/CurrentAccount.kt deleted file mode 100644 index f62c34b2..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/structures/CurrentAccount.kt +++ /dev/null @@ -1,17 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.structures - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class CurrentAccount( - var uin: Long, - var isLogin: Boolean, - var nick: String = "" -) - -@Serializable -data class StdAccount( - @SerialName("user_id") var userId: Long, - @SerialName("nickname") var nick: String = "" -) \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/structures/ErrorCatch.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/structures/ErrorCatch.kt deleted file mode 100644 index add48fd6..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/structures/ErrorCatch.kt +++ /dev/null @@ -1,9 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.structures - -import kotlinx.serialization.Serializable - -@Serializable -data class ErrorCatch( - var url: String, - var error: String -) diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/structures/IndexData.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/structures/IndexData.kt deleted file mode 100644 index 6f15f9b3..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/structures/IndexData.kt +++ /dev/null @@ -1,11 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.structures - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class IndexData( - var version: String, - var startTime: Long, - @SerialName("http_version") var httpVersion: String -) diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/structures/Protocol.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/structures/Protocol.kt deleted file mode 100644 index b660b465..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/structures/Protocol.kt +++ /dev/null @@ -1,43 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.structures - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class Protocol( - var processName: String, - - var subAppId: Long, - var qua: String, - var ntVersion: Int, - - var msfConnNetType: Int, - - var qimei: String, - var svnVersion: String, - - var androidId: String, - var guid: String, - var ksid: String, - var netType: Int, - - - var pingVersion: Byte, - var ssoVer: Int, - - var ssoVersion: Int, - var dbVersion: Int, - - var SSOVer: Int, - var tgtgtVer: Int, - - var androidDevInfo: String, - - @SerialName("signDtConfig") var qSignDtConfig: QSignDtConfig? = null -) - -@Serializable -data class QSignDtConfig( - val en: Array, - val de: Array -) \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/structures/SendMsgResult.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/structures/SendMsgResult.kt deleted file mode 100644 index 976185ad..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/structures/SendMsgResult.kt +++ /dev/null @@ -1,8 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.structures - -data class SendMsgResult( - val msgHashId: Int, - val qqMsgId: Long, - var msgTime: Long, - var isTimeout: Boolean = false -) diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/tools/KtorServer.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/tools/KtorServer.kt deleted file mode 100644 index a88d101e..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/tools/KtorServer.kt +++ /dev/null @@ -1,366 +0,0 @@ -package moe.fuqiuluo.shamrock.tools - -import io.ktor.http.ContentType -import io.ktor.http.Parameters -import io.ktor.server.application.ApplicationCall -import io.ktor.server.application.call -import io.ktor.server.request.contentType -import io.ktor.server.request.receiveParameters -import io.ktor.server.request.receiveText -import io.ktor.server.response.respond -import io.ktor.server.routing.Routing -import io.ktor.server.routing.get -import io.ktor.server.routing.post -import io.ktor.util.AttributeKey -import io.ktor.util.pipeline.PipelineContext -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.jsonObject -import moe.fuqiuluo.shamrock.helper.ParamsException -import io.ktor.http.HttpMethod -import io.ktor.http.parseUrlEncodedParameters -import io.ktor.server.request.httpMethod -import io.ktor.server.routing.route -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.shamrock.remote.structures.CommonResult -import moe.fuqiuluo.shamrock.remote.structures.EmptyObject -import moe.fuqiuluo.shamrock.remote.structures.Status - -@DslMarker -@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS, AnnotationTarget.TYPEALIAS, AnnotationTarget.TYPE) -@Retention(AnnotationRetention.BINARY) -@MustBeDocumented -annotation class ShamrockDsl - - -private val keyIsJson = AttributeKey("isJson") -private val keyJsonObject = AttributeKey("paramsJson") -private val keyJsonArray = AttributeKey("paramsJsonArray") -private val keyJsonElement = AttributeKey("paramsJsonElement") -private val keyParts = AttributeKey("paramsParts") - -suspend fun ApplicationCall.fetch(key: String): String { - val isPost = request.httpMethod == HttpMethod.Post - return if (isPost) { - fetchPost(key) - } else { - fetchGet(key) - } -} - -suspend fun ApplicationCall.fetchOrNull(key: String): String? { - val isPost = request.httpMethod == HttpMethod.Post - return if (isPost) { - fetchPostOrNull(key) - } else { - fetchGetOrNull(key) - } -} - -suspend fun ApplicationCall.fetchOrThrow(key: String): String { - val isPost = request.httpMethod == HttpMethod.Post - return if (isPost) { - fetchPostOrThrow(key) - } else { - fetchGetOrThrow(key) - } -} - -fun ApplicationCall.fetchGet(key: String): String { - return parameters[key]!! -} - -fun ApplicationCall.fetchGetOrNull(key: String): String? { - return parameters[key] -} - -fun ApplicationCall.fetchGetOrThrow(key: String): String { - return parameters[key] ?: throw ParamsException(key) -} - -suspend fun ApplicationCall.fetchPost(key: String): String { - return fetchPostOrNull(key)!! -} - -suspend fun ApplicationCall.fetchPostOrThrow(key: String): String { - return fetchPostOrNull(key) ?: throw ParamsException(key) -} - -fun ApplicationCall.isJsonData(): Boolean { - return ContentType.Application.Json == request.contentType() || ContentType.Application.ProblemJson == request.contentType() -} - -suspend fun ApplicationCall.fetchPostOrNull(key: String): String? { - if (attributes.contains(keyJsonObject)) { - return attributes[keyJsonObject][key].asStringOrNull - } - if (attributes.contains(keyParts)) { - return attributes[keyParts][key] - } - return kotlin.runCatching { - if (isJsonData()) { - Json.parseToJsonElement(receiveText()).jsonObject.also { - attributes.put(keyJsonObject, it) - attributes.put(keyIsJson, true) - }[key].asStringOrNull - } else if ( - ContentType.Application.FormUrlEncoded == request.contentType() - ) { - receiveParameters().also { - attributes.put(keyParts, it) - }[key] - } else { - receiveTextAsUnknown(key) - } - }.getOrElse { - throw IllegalArgumentException("JSON数据格式不合法") - } -} - -private suspend fun ApplicationCall.receiveTextAsUnknown(key: String): String? { - return receiveText().let { text -> - if (text.startsWith("{") && text.endsWith("}")) { - Json.parseToJsonElement(text).jsonObject.also { - attributes.put(keyJsonObject, it) - attributes.put(keyIsJson, true) - }[key].asStringOrNull - } else { - text.parseUrlEncodedParameters().also { - attributes.put(keyParts, it) - attributes.put(keyIsJson, false) - }[key] - } - } // receiveText -} - -suspend fun PipelineContext.fetch(key: String): String { - return call.fetch(key) -} - -suspend fun PipelineContext.fetchOrNull(key: String): String? { - return call.fetchOrNull(key) -} - -suspend fun PipelineContext.fetchOrThrow(key: String): String { - return call.fetchOrThrow(key) -} - -fun PipelineContext.fetchGet(key: String): String { - return call.parameters[key]!! -} - -fun PipelineContext.fetchGetOrNull(key: String): String? { - return call.parameters[key] -} - -fun PipelineContext.fetchGetOrThrow(key: String): String { - return call.parameters[key] ?: throw ParamsException(key) -} - - -suspend fun PipelineContext.fetchPost(key: String): String { - return fetchPostOrNull(key)!! -} - -suspend fun PipelineContext.fetchPostOrThrow(key: String): String { - return fetchPostOrNull(key) ?: throw ParamsException(key) -} - -fun PipelineContext.isJsonData(): Boolean { - return ContentType.Application.Json == call.request.contentType() - || (keyIsJson in call.attributes && call.attributes[keyIsJson]) - || (keyJsonElement in call.attributes) -} - -suspend fun PipelineContext.isJsonString(key: String): Boolean { - if (!isJsonData()) return true - val data = if (keyJsonObject in call.attributes) { - call.attributes[keyJsonObject] - } else { - Json.parseToJsonElement(call.receiveText()).jsonObject.also { - call.attributes.put(keyJsonObject, it) - call.attributes.put(keyIsJson, true) - } - } - return data[key] is JsonPrimitive -} - -suspend fun PipelineContext.isJsonObject(key: String): Boolean { - if (!isJsonData()) return false - val data = if (call.attributes.contains(keyJsonObject)) { - call.attributes[keyJsonObject] - } else { - Json.parseToJsonElement(call.receiveText()).jsonObject.also { - call.attributes.put(keyJsonObject, it) - } - } - return data[key] is JsonObject -} - -suspend fun PipelineContext.isJsonArray(key: String): Boolean { - if (!isJsonData()) return false - val data = if (call.attributes.contains(keyJsonObject)) { - call.attributes[keyJsonObject] - } else { - Json.parseToJsonElement(call.receiveText()).jsonObject.also { - call.attributes.put(keyJsonObject, it) - } - } - return data[key] is JsonArray -} - -suspend fun PipelineContext.fetchPostJsonString(key: String): String { - val data = if (call.attributes.contains(keyJsonObject)) { - call.attributes[keyJsonObject] - } else { - Json.parseToJsonElement(call.receiveText()).jsonObject.also { - call.attributes.put(keyJsonObject, it) - } - } - return data[key].asString -} - -suspend fun PipelineContext.fetchPostJsonElement(key: String): JsonElement { - val data = if (call.attributes.contains(keyJsonObject)) { - call.attributes[keyJsonObject] - } else { - Json.parseToJsonElement(call.receiveText()).jsonObject.also { - call.attributes.put(keyJsonObject, it) - } - } - return data[key]!! -} - -suspend fun PipelineContext.fetchPostJsonObject(key: String): JsonObject { - val data = if (call.attributes.contains(keyJsonObject)) { - call.attributes[keyJsonObject] - } else { - Json.parseToJsonElement(call.receiveText()).jsonObject.also { - call.attributes.put(keyJsonObject, it) - } - } - return data[key].asJsonObject -} - -suspend fun PipelineContext.fetchPostJsonObjectOrNull(key: String): JsonObject? { - val data = if (call.attributes.contains(keyJsonObject)) { - call.attributes[keyJsonObject] - } else { - Json.parseToJsonElement(call.receiveText()).jsonObject.also { - call.attributes.put(keyIsJson, true) - call.attributes.put(keyJsonObject, it) - } - } - return data[key].asJsonObjectOrNull -} - -suspend fun PipelineContext.fetchPostJsonElementOrNull(): JsonElement? { - return runCatching { - if (call.attributes.contains(keyJsonObject)) { - call.attributes[keyJsonObject] - } else if (call.attributes.contains(keyJsonArray)) { - call.attributes[keyJsonArray] - } else if (call.attributes.contains(keyJsonElement)) { - call.attributes[keyJsonElement] - } else { - Json.parseToJsonElement(call.receiveText()).also { - call.attributes.put(keyJsonElement, it) - if (it is JsonObject) { - call.attributes.put(keyJsonObject, it) - } else if (it is JsonArray) { - call.attributes.put(keyJsonArray, it) - } - } - } - }.getOrNull() -} - -suspend fun PipelineContext.fetchPostJsonArray(key: String): JsonArray { - val data = if (call.attributes.contains(keyJsonObject)) { - call.attributes[keyJsonObject] - } else { - Json.parseToJsonElement(call.receiveText()).jsonObject.also { - call.attributes.put(keyJsonObject, it) - call.attributes.put(keyIsJson, true) - } - } - return data[key].asJsonArray -} - -suspend fun PipelineContext.fetchPostOrNull(key: String): String? { - return call.fetchPostOrNull(key) -} - -@io.ktor.util.KtorDsl -fun Routing.getOrPost(path: String, body: suspend PipelineContext.(Unit) -> Unit) { - route(path) { - get(body) - post(body) - } -} - -@io.ktor.util.KtorDsl -fun Routing.getOrPost(path: Regex, body: suspend PipelineContext.(Unit) -> Unit) { - route(path) { - get(body) - post(body) - } -} - -@ShamrockDsl -internal suspend inline fun PipelineContext.respond( - isOk: Boolean, - code: Status, - msg: String = "", - echo: JsonElement = EmptyJsonString -) { - call.respond( - CommonResult( - if (isOk) "ok" else "failed", - code.code, - EmptyObject, - msg, - echo - ) - ) -} - -@ShamrockDsl -internal suspend inline fun PipelineContext.respond( - isOk: Boolean, - code: Status, - data: T, - msg: String = "", - echo: JsonElement = EmptyJsonString -) { - call.respond( - CommonResult( - if (isOk) "ok" else "failed", - code.code, - data, - msg, - echo - ) - ) -} - -@ShamrockDsl -internal suspend inline fun PipelineContext.respond( - isOk: Boolean, - code: Int, - data: T, - msg: String = "", - echo: JsonElement = EmptyJsonString -) { - call.respond( - CommonResult( - if (isOk) "ok" else "failed", - code, - data, - msg, - echo - ) - ) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/tools/MutBroadcast.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/tools/MutBroadcast.kt deleted file mode 100644 index 900bd0f7..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/tools/MutBroadcast.kt +++ /dev/null @@ -1,14 +0,0 @@ -package moe.fuqiuluo.shamrock.tools - -import android.content.Context -import android.content.Intent - -/** - * 指定向某个进程发送广播 - */ -internal fun Context.broadcast(processName: String, intentBuilder: Intent.() -> Unit) { - val intent = Intent() - intent.action = "moe.fuqiuluo.$processName.dynamic" - intent.intentBuilder() - sendBroadcast(intent) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/AppRuntimeFetcher.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/AppRuntimeFetcher.kt deleted file mode 100644 index e91df25f..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/AppRuntimeFetcher.kt +++ /dev/null @@ -1,13 +0,0 @@ -package moe.fuqiuluo.shamrock.xposed.helper - -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import mqq.app.AppRuntime -import mqq.app.MobileQQ - -internal object AppRuntimeFetcher { - val appRuntime: AppRuntime - get() = if (PlatformUtils.isMqqPackage()) - MobileQQ.getMobileQQ().waitAppRuntime() - else - MobileQQ.getMobileQQ().waitAppRuntime(null) -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/NTServiceFetcher.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/NTServiceFetcher.kt deleted file mode 100644 index 471a1843..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/NTServiceFetcher.kt +++ /dev/null @@ -1,107 +0,0 @@ -package moe.fuqiuluo.shamrock.xposed.helper - -import com.tencent.qqnt.kernel.api.IKernelService -import com.tencent.qqnt.kernel.api.impl.MsgService -import com.tencent.qqnt.kernel.nativeinterface.IOperateCallback -import com.tencent.qqnt.kernel.nativeinterface.IQQNTWrapperSession -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.remote.service.PacketReceiver -import moe.fuqiuluo.shamrock.remote.service.listener.AioListener -import moe.fuqiuluo.shamrock.remote.service.listener.PrimitiveListener -import moe.fuqiuluo.shamrock.tools.hookMethod -import moe.fuqiuluo.shamrock.utils.PlatformUtils - -internal object NTServiceFetcher { - private lateinit var iKernelService: IKernelService - private val lock = Mutex() - private var curKernelHash = 0 - - suspend fun onFetch(service: IKernelService) { - lock.withLock { - val msgService = service.msgService ?: return - val sessionService = service.wrapperSession ?: return - //val groupService = sessionService.groupService ?: return - - val curHash = service.hashCode() + msgService.hashCode() - if (isInitForNt(curHash)) return - - PacketHandler.initPacketHandler() - PacketReceiver.init() - - LogCenter.log("Fetch kernel service successfully: $curKernelHash,$curHash,${PlatformUtils.isMainProcess()}") - curKernelHash = curHash - this.iKernelService = service - - - initNTKernelListener(msgService) - antiBackgroundMode(sessionService) - //hookGuildListener(sessionService) - } - } - - /* - private fun hookGuildListener(sessionService: IQQNTWrapperSession) { - val guildService = sessionService.guildService - XposedBridge.hookMethod(guildService::addKernelGuildListener.javaMethod, object: XC_MethodHook() { - override fun beforeHookedMethod(param: MethodHookParam?) { - val service = param?.thisObject as IKernelGuildService - service.addKernelGuildListener(KernelGuildListener) - LogCenter.log("Register Guild listener successfully.") - } - }) - } - */ - - private inline fun isInitForNt(hash: Int): Boolean { - return hash == curKernelHash - } - - private fun initNTKernelListener(msgService: MsgService) { - if (!PlatformUtils.isMainProcess()) return - - try { - LogCenter.log("Register MSG listener successfully.") - msgService.addMsgListener(AioListener) - - // 接口缺失 暂不使用 - //groupService.addKernelGroupListener(GroupEventListener) - //LogCenter.log("Register Group listener successfully.") - - PrimitiveListener.registerListener() - } catch (e: Throwable) { - LogCenter.log(e.stackTraceToString(), Level.WARN) - } - } - - private fun antiBackgroundMode(sessionService: IQQNTWrapperSession) { - try { - sessionService.javaClass.hookMethod("switchToBackGround").before { - LogCenter.log({ "阻止进入后台模式!" }, Level.DEBUG) - it.result = null - } - - val msgService = sessionService.msgService - msgService.javaClass.hookMethod("switchBackGroundForMqq").before { - LogCenter.log({ "阻止进入后台模式!" }, Level.DEBUG) - val cb = it.args[1] as IOperateCallback - cb.onResult(-1, "injected") - it.result = null - } - msgService.javaClass.hookMethod("switchBackGround").before { - LogCenter.log({ "阻止进入后台模式!" }, Level.DEBUG) - val cb = it.args[1] as IOperateCallback - cb.onResult(-1, "injected") - it.result = null - } - LogCenter.log({ "反后台模式注入成功!" }, Level.DEBUG) - } catch (e: Throwable) { - LogCenter.log("Keeping NT alive failed: ${e.message}", Level.WARN) - } - } - - val kernelService: IKernelService - get() = iKernelService -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/NTServiceHelper.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/NTServiceHelper.kt deleted file mode 100644 index c9877589..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/NTServiceHelper.kt +++ /dev/null @@ -1,19 +0,0 @@ -package moe.fuqiuluo.shamrock.xposed.helper - -import com.tencent.qqnt.kernel.api.IKernelService -import com.tencent.qqnt.kernel.api.impl.MsgService -import java.lang.reflect.Method - -internal object KernelServiceHelper { - private lateinit var M_GET_MSG_SERVICE: Method - - fun getMsgService(service: IKernelService): MsgService? { - if (!KernelServiceHelper::M_GET_MSG_SERVICE.isInitialized) { - M_GET_MSG_SERVICE = IKernelService::class.java.getMethod("getMsgService") - } - return M_GET_MSG_SERVICE.invoke(service) as? MsgService - } -} - -internal val IKernelService.msgService: MsgService? - get() = KernelServiceHelper.getMsgService(this) \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/PacketHandler.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/PacketHandler.kt deleted file mode 100644 index 53263645..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/PacketHandler.kt +++ /dev/null @@ -1,59 +0,0 @@ -package moe.fuqiuluo.shamrock.xposed.helper - -import moe.fuqiuluo.shamrock.tools.broadcast -import moe.fuqiuluo.shamrock.xposed.helper.internal.DynamicReceiver -import moe.fuqiuluo.shamrock.xposed.helper.internal.IPCRequest -import mqq.app.MobileQQ - -internal object PacketHandler { - /* - MSF 进程包处理是否就绪 - */ - var isInit = false - - internal fun initPacketHandler() { - DynamicReceiver.register("msf_waiter", IPCRequest { - isInit = true - }) - } - - /** - * 注册常驻包处理器 - */ - fun register(cmd: String, callback: (Int, ByteArray) -> Unit) { - // 在本地广播接收器注册对应处理器 - DynamicReceiver.register(cmd, IPCRequest { - val buffer = it.getByteArrayExtra("buffer")!! - val seq = it.getIntExtra("seq", 0) - callback(seq, buffer) - }) - if (!isInit) return - // 向MSF进程广播要求添加处理器 - MobileQQ.getContext().broadcast("msf") { - putExtra("__cmd", "register_handler_cmd") - putExtra("handler_cmd", cmd) - } - } - - suspend fun registerLessHandler(cmd: String, seq: Int, callback: (Int, ByteArray) -> Unit): Int { - DynamicReceiver.register(IPCRequest(cmd, seq) { - val buffer = it.getByteArrayExtra("buffer")!! - val currSeq = it.getIntExtra("seq", 0) - callback(currSeq, buffer) - }) - return seq - } - - suspend fun unregisterLessHandler(seq: Int) { - DynamicReceiver.unregister(seq) - } - - fun unregister(cmd: String) { - DynamicReceiver.unregister(cmd) - if (!isInit) return - MobileQQ.getContext().broadcast("msf") { - putExtra("__cmd", "unregister_handler_cmd") - putExtra("handler_cmd", cmd) - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/internal/DataRequester.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/internal/DataRequester.kt deleted file mode 100644 index f2a66c99..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/internal/DataRequester.kt +++ /dev/null @@ -1,113 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) - -package moe.fuqiuluo.shamrock.xposed.helper.internal - -import android.content.ContentValues -import android.content.Intent -import kotlinx.atomicfu.atomic -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import moe.fuqiuluo.shamrock.xposed.helper.AppTalker -import java.util.Timer -import kotlin.concurrent.timer - -/** - * 数据请求中心 - * 支持应用内数据传递,以及外部向内部传入 - */ -object DataRequester { - private val seqFactory = atomic(0) - private val seq: Int - get() { - if (seqFactory.value > 1000000) { - seqFactory.lazySet(0) - } - return seqFactory.incrementAndGet() - } - - suspend fun request( - cmd: String, - currSeq: Int = seq, - values: Map? = null, - onFailure: ((Throwable) -> Unit)? = null, - callback: ICallback? = null - ): Int { - return request(cmd, currSeq, bodyBuilder = { - values?.forEach { (key, value) -> - when (value) { - is Int -> this.put(key, value) - is Long -> this.put(key, value) - is Short -> this.put(key, value) - is Byte -> this.put(key, value) - is String -> this.put(key, value) - is ByteArray -> this.put(key, value) - is Boolean -> this.put(key, value) - is Float -> this.put(key, value) - is Double -> this.put(key, value) - } - } - }, onFailure, callback) - } - - suspend fun request( - cmd: String, - currentSeq: Int = seq, - bodyBuilder: (ContentValues.() -> Unit)? = null, - onFailure: ((Throwable) -> Unit)? = null, - callback: ICallback? = null - ): Int { - val values = ContentValues() - bodyBuilder?.invoke(values) - values.put("__hash", (cmd + currentSeq).hashCode()) - values.put("__cmd", cmd) - AppTalker.talk(values, onFailure) - if (callback != null) { - val timer: Timer = timer(initialDelay = 3000L, period = 5000L) { - GlobalScope.launch(Dispatchers.Default) { - DynamicReceiver.unregister(currentSeq) - cancel() - } - } - val request = IPCRequest(cmd, currentSeq, values) { - try { - timer.cancel() - } finally { - callback.handle(it) - } - } - DynamicReceiver.register(request) - } - return currentSeq - } -} - -fun interface ICallback { - suspend fun handle(intent: Intent) -} - -data class IPCRequest( - val cmd: String = "", - val seq: Int = -1, - val values: ContentValues? = null, - var callback: ICallback? = null, -) { - override fun hashCode(): Int { - return (cmd + seq).hashCode() - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as IPCRequest - - if (cmd != other.cmd) return false - if (seq != other.seq) return false - if (values != other.values) return false - if (callback != other.callback) return false - - return true - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/internal/DynamicReceiver.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/internal/DynamicReceiver.kt deleted file mode 100644 index aa041600..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/helper/internal/DynamicReceiver.kt +++ /dev/null @@ -1,78 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) -package moe.fuqiuluo.shamrock.xposed.helper.internal - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter - -/** - * 动态广播 - */ -internal object DynamicReceiver: BroadcastReceiver() { - private val hashHandler = mutableSetOf() - private val cmdHandler = mutableMapOf() - private val mutex = Mutex() // 滥用的锁,尽量减少使用 - - override fun onReceive(ctx: Context, intent: Intent) { - GlobalScope.launch(Dispatchers.Default) { - val hash = intent.getIntExtra("__hash", -1) - val cmd = intent.getStringExtra("__cmd") ?: "" - try { - if (cmd.isNotBlank()) { - cmdHandler[cmd].also { - if (it == null) - LogCenter.log("无常驻包处理器: $cmd, main = ${PlatformUtils.isMainProcess()}", Level.ERROR) - }?.callback?.handle(intent) - } else if (hash != -1) { - mutex.withLock { - hashHandler.removeIf { - if (hash == it.hashCode()) { - GlobalScope.launch { - it.callback?.handle(intent) - } - return@removeIf it.seq != -1 - } - return@removeIf false - } - } - } - } catch (e: Throwable) { - LogCenter.log("包处理器[$cmd]错误: $e", Level.ERROR) - } - } - } - - fun register(cmd: String, request: IPCRequest) { - cmdHandler[cmd] = request - } - - fun unregister(cmd: String) { - cmdHandler.remove(cmd) - } - - /*** - * 注册临时包处理器 - */ - suspend fun register(request: IPCRequest) { - LogCenter.log({ "registerHandler[${request.hashCode()}](cmd = ${request.cmd}, seq = ${request.seq})" }, Level.DEBUG) - - mutex.withLock { - hashHandler.add(request) - } - } - - suspend fun unregister(seq: Int) { - mutex.withLock { - hashHandler.removeIf { it.seq == seq } - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/AntiDetection.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/AntiDetection.kt deleted file mode 100644 index 507a3b1f..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/AntiDetection.kt +++ /dev/null @@ -1,248 +0,0 @@ -@file:Suppress("UNCHECKED_CAST", "LocalVariableName") -package moe.fuqiuluo.shamrock.xposed.hooks - -import android.content.ContentResolver -import android.content.Context -import android.content.pm.PackageManager -import android.content.pm.VersionedPackage -import android.os.Build -import android.os.Looper -import de.robv.android.xposed.XC_MethodReplacement -import de.robv.android.xposed.XSharedPreferences -import de.robv.android.xposed.XposedHelpers -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig -import moe.fuqiuluo.shamrock.tools.MethodHooker -import moe.fuqiuluo.shamrock.tools.hookMethod -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 - -@XposedHook(priority = 0) -class AntiDetection: IAction { - private external fun antiNativeDetections(): Boolean - - override fun invoke(ctx: Context) { - try { - antiFindPackage(ctx) - }catch(_:Throwable){ } //某个大聪明在外面隐藏了shamrock,导致这个代码抛出异常,俺不说是谁>_< - antiGetPackageGidsDetection(ctx) - antiProviderDetection() - antiNativeDetection() - if (ShamrockConfig.isAntiTrace()) - antiTrace() - antiMemoryWalking() - } - - private fun antiGetPackageGidsDetection(ctx: Context) { - //通过 android.content.pm.PackageManager->getPackageGids(Ljava/lang/String;)[I 扫 moe.fuqiuluo.shamrock - ctx.packageManager::class.java.hookMethod("getPackageGids").before { - val packageName = it.args[0] as String - if (packageName == "moe.fuqiuluo.shamrock") { - it.result = null - it.throwable = PackageManager.NameNotFoundException(packageName) - LogCenter.log("AntiDetection: 检测到对Shamrock的检测,欺骗GetPackageGids") - } - } - } - - private fun antiProviderDetection() { - ContentResolver::class.java.hookMethod("acquireContentProviderClient").before { - val uri = it.args[0] as String - if (uri == "moe.fuqiuluo.108.provider" || uri == "moe.fuqiuluo.xqbot.provider") { - it.result = null - LogCenter.log("AntiDetection: 检测到对Shamrock的检测,欺骗ContentResolver", Level.WARN) - } - //else { - // LogCenter.log(uri) - //} - } - } - - val isModuleStack = fun String.(): Boolean { - return contains("fuqiuluo") || contains("shamrock") || contains("whitechi") || contains("lsposed") || contains("xposed") - } - - private fun isModuleStack(): Boolean { - Thread.currentThread().stackTrace.forEach { - if (it.className.isModuleStack()) return true - } - return false - } - - private fun antiNativeDetection() { - try { - val pref = XSharedPreferences("moe.fuqiuluo.shamrock", "shared_config") - if (!pref.file.canRead()) { - LogCenter.log("[Shamrock] unable to load XSharedPreferences", Level.WARN) - return - } else if (!pref.getBoolean("super_anti", false)) { - return - } - NativeLoader.load("clover") - val env = XposedEntry.hasEnv() - val injected = XposedEntry.injected() - if (!env || !injected) { - LogCenter.log("[Shamrock] Shamrock反检测启动失败(env=$env, injected=$injected)", Level.ERROR) - } else { - XposedEntry.secStaticNativehookInited = true - LogCenter.log("[Shamrock] Shamrock反检测启动成功: ${antiNativeDetections()}", Level.INFO) - } - } catch (e: Throwable) { - LogCenter.log("[Shamrock] Shamrock反检测启动失败,请检查LSPosed版本使用大于100: ${e.message}", Level.ERROR) - } - } - - private fun antiFindPackage(context: Context) { - if (isAntiFindPackage) return - - val packageManager = context.packageManager - val applicationInfo = packageManager.getApplicationInfo("moe.fuqiuluo.shamrock", 0) - val packageInfo = packageManager.getPackageInfo("moe.fuqiuluo.shamrock", 0) - - packageManager.javaClass.hookMethod("getApplicationInfo").before { - val packageName = it.args[0] as String - if(packageName == "moe.fuqiuluo.shamrock") { - LogCenter.log("AntiDetection: 检测到对Shamrock的检测,欺骗PackageManager(GA)", Level.WARN) - it.throwable = PackageManager.NameNotFoundException("Hided") - } else if (packageName == "moe.fuqiuluo.shamrock.hided") { - it.result = applicationInfo - } - } - - packageManager.javaClass.hookMethod("getPackageInfo").before { - when(val packageName = it.args[0]) { - is String -> { - if(packageName == "moe.fuqiuluo.shamrock") { - LogCenter.log("AntiDetection: 检测到对Shamrock的检测,欺骗PackageManager(GP)", Level.WARN) - it.throwable = PackageManager.NameNotFoundException() - } else if (packageName == "moe.fuqiuluo.shamrock.hided") { - it.result = packageInfo - } - } - else -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && packageName is VersionedPackage) { - if(packageName.packageName == "moe.fuqiuluo.shamrock") { - LogCenter.log("AntiDetection: 检测到对Shamrock的检测,欺骗PackageManager(GPV)", Level.WARN) - it.throwable = PackageManager.NameNotFoundException() - } - } - } - } - } - - isAntiFindPackage = true - } - - private fun antiMemoryWalking() { - val c = Class.forName("dalvik.system.VMDebug") - //val startMethodTracingMethod = c.getDeclaredMethod( - // "startMethodTracing", String::class.java, - // Integer.TYPE, Integer.TYPE, java.lang.Boolean.TYPE, Integer.TYPE - //) - //val stopMethodTracingMethod = c.getDeclaredMethod("stopMethodTracing") - //val getMethodTracingModeMethod = c.getDeclaredMethod("getMethodTracingMode") - //val getRuntimeStatMethod = c.getDeclaredMethod("getRuntimeStat", String::class.java) - //val getRuntimeStatsMethod = c.getDeclaredMethod("getRuntimeStats") - val VMClassLoader = LuoClassloader.load("java/lang/VMClassLoader") - if (VMClassLoader != null) { - // ... - } - - kotlin.runCatching { - XposedHelpers.findAndHookMethod(XposedHelpers.findClass("com.tencent.bugly.agent.CrashReport", LuoClassloader.hostClassLoader), - "initCrashReport", object: XC_MethodReplacement() { - override fun replaceHookedMethod(param: MethodHookParam): Any? { - return null - } - }) - } - - c.hookMethod("countInstancesOfClass").before { - val clz = it.args[0] as Class<*> - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - if(clz.packageName.isModuleStack()) { - it.result = 0L - } - } else { - if(clz.canonicalName?.isModuleStack() == true) { - it.result = 0L - } - } - } - - c.hookMethod("countInstancesOfClasses").before { - val clzs = it.args[0] as Array> - clzs.forEach { clz -> - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - if(clz.packageName.isModuleStack()) { - it.result = 0L - return@forEach - } - } else { - if(clz.canonicalName?.isModuleStack() == true) { - it.result = 0L - return@forEach - } - } - } - } - - c.hookMethod("getInstancesOfClasses").after { - val clzs = it.args[0] as Array> - clzs.forEachIndexed { _, clz -> - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - if(clz.packageName.isModuleStack()) { - it.result = Array(0) { } - } - } else { - if(clz.canonicalName?.isModuleStack() == true) { - it.result = Array(0) { } - } - } - } - } - } - - private fun antiTrace() { - val isModuleStack = fun StackTraceElement.(): Boolean { - return className.isModuleStack() - } - - val stackTraceHooker: MethodHooker = { - val result = it.result as Array - var zygote = false - val newResult = result.filter { - if (it.className == ZYGOTE_NAME) { - zygote = true - } - !it.isModuleStack() - }.toTypedArray() - if (!zygote && Thread.currentThread() == Looper.getMainLooper().thread) { - it.result = arrayListOf(StackTraceElement(ZYGOTE_NAME, "main", ZYGOTE_NAME, 0), *newResult) - } else { - it.result = newResult - } - } - - Thread::class.java.hookMethod("getName").after { - val result = it.result as String - if (result.contains("fuqiuluo") || result.contains("shamrock") || result.contains("whitechi")) { - it.result = "android" - } - } - - Thread::class.java.hookMethod("getStackTrace").after(stackTraceHooker) - Throwable::class.java.hookMethod("getStackTrace").after(stackTraceHooker) - Throwable::class.java.hookMethod("getOurStackTrace").after(stackTraceHooker) - } - - companion object { - @JvmStatic - var isAntiFindPackage = false - - const val ZYGOTE_NAME = "com.android.internal.os.ZygoteInit" - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/DataReceiver.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/DataReceiver.kt deleted file mode 100644 index fedd7b75..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/DataReceiver.kt +++ /dev/null @@ -1,67 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) -package moe.fuqiuluo.shamrock.xposed.hooks - -import android.annotation.SuppressLint -import android.content.Context -import android.content.IntentFilter -import android.os.Build -import android.os.Handler -import android.widget.Toast -import de.robv.android.xposed.XposedBridge -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import moe.fuqiuluo.shamrock.xposed.helper.internal.DynamicReceiver -import moe.fuqiuluo.symbols.XposedHook -import mqq.app.MobileQQ - -internal lateinit var GlobalUi: Handler - -internal fun Context.toast(msg: String, flag: Int = Toast.LENGTH_SHORT) { - XposedBridge.log(msg) - if (!::GlobalUi.isInitialized) { - return - } - GlobalUi.post { Toast.makeText(this, msg, flag).show() } -} - -@XposedHook(priority = 0) -internal class DataReceiver: IAction { - @SuppressLint("UnspecifiedRegisterReceiverFlag") - override fun invoke(ctx: Context) { - kotlin.runCatching { - MobileQQ.getMobileQQ().unregisterReceiver(DynamicReceiver) - } - - if (PlatformUtils.isMainProcess()) { - GlobalUi = Handler(ctx.mainLooper) - GlobalScope.launch { - val intentFilter = IntentFilter() - intentFilter.addAction("moe.fuqiuluo.xqbot.dynamic") - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - MobileQQ.getMobileQQ().registerReceiver( - DynamicReceiver, intentFilter, - Context.RECEIVER_EXPORTED - ) - } else { - MobileQQ.getMobileQQ().registerReceiver(DynamicReceiver, intentFilter) - } - XposedBridge.log("Register Main::Broadcast successfully.") - } - } else if (PlatformUtils.isMsfProcess()) { - val intentFilter = IntentFilter() - intentFilter.addAction("moe.fuqiuluo.msf.dynamic") - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - MobileQQ.getMobileQQ().registerReceiver( - DynamicReceiver, intentFilter, - Context.RECEIVER_EXPORTED - ) - } else { - MobileQQ.getMobileQQ().registerReceiver(DynamicReceiver, intentFilter) - } - } - } -} - diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/FetchService.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/FetchService.kt deleted file mode 100644 index cebe42ab..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/FetchService.kt +++ /dev/null @@ -1,37 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) -package moe.fuqiuluo.shamrock.xposed.hooks - -import android.content.Context -import com.tencent.qqnt.kernel.api.IKernelService -import com.tencent.qqnt.kernel.api.impl.KernelServiceImpl -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import moe.fuqiuluo.shamrock.tools.hookMethod -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher -import moe.fuqiuluo.shamrock.xposed.loader.NativeLoader -import moe.fuqiuluo.symbols.XposedHook - -@XposedHook(priority = 2) -internal class FetchService: IAction { - override fun invoke(ctx: Context) { - NativeLoader.load("shamrock") - - if (PlatformUtils.isMqq()) { - KernelServiceImpl::class.java.hookMethod("initService").after { - val service = it.thisObject as IKernelService - LogCenter.log("NTKernel try to init service: $service", Level.DEBUG) - GlobalScope.launch { - NTServiceFetcher.onFetch(service) - } - } - } else if (PlatformUtils.isTim()) { - // TIM 尚未进入 NTKernel - LogCenter.log("NTKernel init failed: tim not support NT", Level.ERROR) - } - - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/FixLibraryLoad.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/FixLibraryLoad.kt deleted file mode 100644 index 1eaa4ced..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/FixLibraryLoad.kt +++ /dev/null @@ -1,34 +0,0 @@ -package moe.fuqiuluo.shamrock.xposed.hooks - -import android.content.Context -import moe.fuqiuluo.shamrock.tools.hookMethod -import moe.fuqiuluo.shamrock.xposed.loader.NativeLoader -import moe.fuqiuluo.symbols.XposedHook - -@XposedHook(priority = 0) -internal class FixLibraryLoad: IAction { - val redirectedLibrary =arrayOf( - "ffmpegkit_abidetect", - "avutil", - "swscale", - "swresample", - "avcodec", - "avformat", - "avfilter", - "avdevice", - "ffmpegkit" - ) - - override fun invoke(ctx: Context) { - com.arthenica.ffmpegkit.NativeLoader::class.java.hookMethod("loadLibrary").before { - val name: String = it.args[0] as String - if (name in redirectedLibrary) { - redirectedLibrary.forEach { - NativeLoader.load(it) - } - NativeLoader.load(name) - } - it.result = null - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/ForceTablet.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/ForceTablet.kt deleted file mode 100644 index 834bda6f..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/ForceTablet.kt +++ /dev/null @@ -1,65 +0,0 @@ -@file:Suppress("UNUSED_VARIABLE", "LocalVariableName") -package moe.fuqiuluo.shamrock.xposed.hooks - -import android.content.Context -import com.tencent.common.config.pad.DeviceType -import com.tencent.qqnt.kernel.nativeinterface.InitSessionConfig -import de.robv.android.xposed.XposedBridge -import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig -import moe.fuqiuluo.shamrock.tools.FuzzySearchClass -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.tools.afterHook -import moe.fuqiuluo.shamrock.tools.hookMethod -import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader -import moe.fuqiuluo.symbols.XposedHook - -@XposedHook(priority = 0) -internal class ForceTablet: IAction { - override fun invoke(ctx: Context) { - //if (!PlatformUtils.isMqqPackage()) return - if (ShamrockConfig.forceTablet()) { - if (PlatformUtils.isMainProcess()) { - LogCenter.log("强制协议类型 (PAD)", toast = true) - } - - val returnTablet = afterHook { - it.result = DeviceType.TABLET - } - - FuzzySearchClass.findAllClassByMethod( - LuoClassloader.hostClassLoader, "com.tencent.common.config.pad" - ) { _, method -> - method.returnType == DeviceType::class.java - }.forEach { clazz -> - //log("Inject to tablet mode in ${clazz.name}") - val method = clazz.declaredMethods.first { it.returnType == DeviceType::class.java } - XposedBridge.hookMethod(method, returnTablet) - } - - val PadUtil = LuoClassloader.load("com.tencent.common.config.pad.PadUtil") - PadUtil?.declaredMethods?.filter { - it.returnType == DeviceType::class.java - }?.forEach { - XposedBridge.hookMethod(it, returnTablet) - } - - val deviceTypeField = InitSessionConfig::class.java.declaredFields.firstOrNull { - it.type == com.tencent.qqnt.kernel.nativeinterface.DeviceType::class.java - } - if (deviceTypeField != null) { - XposedBridge.hookAllConstructors(InitSessionConfig::class.java, afterHook { - if (!deviceTypeField.isAccessible) deviceTypeField.isAccessible = true - deviceTypeField.set(it.thisObject, com.tencent.qqnt.kernel.nativeinterface.DeviceType.KPAD) - }) - } - InitSessionConfig::class.java.hookMethod("getDeviceType").after { - it.result = com.tencent.qqnt.kernel.nativeinterface.DeviceType.KPAD - } - - //InitSessionConfig::class.java.hookMethod("getPlatform").after { - // it.result = PlatformType.KMAC - //} - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/GuidLock.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/GuidLock.kt deleted file mode 100644 index 54a36b35..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/GuidLock.kt +++ /dev/null @@ -1,75 +0,0 @@ -package moe.fuqiuluo.shamrock.xposed.hooks - -import android.content.Context -import com.tencent.beacon.event.open.BeaconReport -import com.tencent.mobileqq.qsec.qsecurity.QSecConfig -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.tools.hex2ByteArray -import moe.fuqiuluo.shamrock.tools.hookMethod -import moe.fuqiuluo.shamrock.utils.MMKVFetcher -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import moe.fuqiuluo.symbols.XposedHook -import oicq.wlogin_sdk.tools.util - -@XposedHook(priority = 10) -internal class GuidLock: IAction { - companion object { - var qimei: String = "" - } - - override fun invoke(ctx: Context) { - val guildLock = MMKVFetcher.mmkvWithId("guid") - val utilClass = util::class.java - utilClass.hookMethod("needChangeGuid").before { - if (guildLock.getString("guid", null) != null) { - it.result = false - } - } - utilClass.hookMethod("getGuidFromFile").before { - val guid = guildLock.getString("guid", null) - if (guid != null) { - it.result = guid.hex2ByteArray() - } - } - utilClass.hookMethod("saveGuidToFile").before { - val guid = guildLock.getString("guid", null) - if (guid != null) { - it.args[1] = guid.hex2ByteArray() - } - } - - utilClass.hookMethod("get_last_guid").before { - val guid = guildLock.getString("guid", null) - if (guid != null) { - it.result = guid.hex2ByteArray() - } - } - - utilClass.hookMethod("generateGuid").before { - val guid = guildLock.getString("guid", null) - if (guid != null) { - it.result = guid.hex2ByteArray() - } - } - - QSecConfig::class.java.hookMethod("setupBusinessInfo").before { - val guid = guildLock.getString("guid", null) - if (guid != null) { - it.args[2] = guid.hex2ByteArray() - } - } - - if (PlatformUtils.isMqqPackage()) { - BeaconReport.getInstance().getQimei("0S200MNJT807V3GE", ctx) { qimei -> - LogCenter.log("QIMEI获取: ${qimei.qimei36}") - GuidLock.qimei = qimei.qimei36 - } - } else { - BeaconReport.getInstance().getQimei { qimei -> - LogCenter.log("QIMEI获取: ${qimei.qimei36}") - GuidLock.qimei = qimei.qimei36 - } - } - - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/HookForDebug.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/HookForDebug.kt deleted file mode 100644 index 24e55364..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/HookForDebug.kt +++ /dev/null @@ -1,73 +0,0 @@ -@file:Suppress("UNUSED_VARIABLE", "LocalVariableName") - -package moe.fuqiuluo.shamrock.xposed.hooks - -import android.content.Context -import com.tencent.mobileqq.perf.block.BinderMethodProxy -import com.tencent.mobileqq.qmmkv.MMKVOptionEntity -import com.tencent.mobileqq.qroute.QRoute -import com.tencent.qqnt.aio.api.IAIOPicDownloaderProvider -import de.robv.android.xposed.XposedBridge -import epic.EIPCClient -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.tools.beforeHook -import moe.fuqiuluo.shamrock.tools.hookMethod -import moe.fuqiuluo.shamrock.tools.toInnerValuesString -import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader -import moe.fuqiuluo.symbols.Process -import moe.fuqiuluo.symbols.XposedHook -import java.lang.reflect.Modifier - -@XposedHook(priority = -1, process = Process.ALL) -internal class HookForDebug: IAction { - override fun invoke(ctx: Context) { - //val LibraDownloader = QRoute.api(IAIOPicDownloaderProvider::class.java).provideDownloader().javaClass - //LibraDownloader.hookMethod("downLoad").before { - // val option = it.args[0] - // LogCenter.log("LibraDownloader.downLoad(${option.toInnerValuesString()})") - //} - } -} - -/*val NtDnsManager = LuoClassloader.load("com.tencent.qqnt.dns.NtDnsManager")!! - val NtDnsInternal = NtDnsManager.declaredMethods.first { - !Modifier.isStatic(it.modifiers) && it.parameterCount == 0 - }.returnType - XposedBridge.hookMethod(NtDnsInternal.declaredMethods.first { - it.parameterCount == 2 - && it.parameterTypes[0] == String::class.java - && it.parameterTypes[1] == Int::class.java - && it.returnType == ArrayList::class.java - }, beforeHook { - val domain = it.args[0] as String - val type = it.args[1] as Int - LogCenter.log("NtDnsManager: reqDomain2IpList($domain, $type)") - LogCenter.log(Exception().stackTraceToString()) - })*/ -/* - val httpEngineService = AppRuntimeFetcher.appRuntime - .getRuntimeService(IHttpEngineService::class.java, "all") - httpEngineService.javaClass.hookMethod("sendReq").before { - if (it.args[0] is HttpNetReq) { - val req = it.args[0] as HttpNetReq - if (req.mReqProperties["Shamrock"] == "true") { - return@before - } - LogCenter.log("已记录一个IHttpEngineService请求") - LogCenter.log("请求地址: ${req.mReqUrl}") - LogCenter.log("请求: ${req.toInnerValuesString(NetReq::class.java)}") - } - } - BinderMethodProxy::class.java.hookMethod("callServer").before { - val action = it.args[2] as String - if (action == "reqDomain2IpList") { - LogCenter.log(Exception().stackTraceToString()) - } - } - EIPCClient::class.java.hookMethod("callServer").before { - val module = it.args[0] as String - val action = it.args[1] as String - if (action == "reqDomain2IpList" || module.contains("dns", ignoreCase = true)) { - LogCenter.log(Exception().stackTraceToString()) - } - }*/ diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/HookWrapperCodec.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/HookWrapperCodec.kt deleted file mode 100644 index 2b8a37df..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/HookWrapperCodec.kt +++ /dev/null @@ -1,139 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) -package moe.fuqiuluo.shamrock.xposed.hooks - -import android.content.Context -import com.tencent.msf.service.protocol.pb.SSOLoginMerge -import com.tencent.qphone.base.remote.FromServiceMsg -import com.tencent.qphone.base.remote.ToServiceMsg -import com.tencent.qphone.base.util.CodecWarpper -import kotlinx.atomicfu.atomic -import kotlinx.coroutines.DelicateCoroutinesApi -import moe.fuqiuluo.shamrock.remote.service.PacketReceiver -import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig -import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY -import moe.fuqiuluo.shamrock.tools.hookMethod -import moe.fuqiuluo.shamrock.tools.slice -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.xposed.helper.internal.DynamicReceiver -import moe.fuqiuluo.shamrock.xposed.helper.internal.IPCRequest -import moe.fuqiuluo.symbols.XposedHook - -private const val MAGIC_APP_ID = 114514 - -@XposedHook(priority = 2) -internal class HookWrapperCodec: IAction { - private val IgnoredCmd = arrayOf( - "trpc.sq_adv.official_account_adv_push.OfficialAccountAdvPush.AdvPush", - "LightAppSvc.mini_app_report_transfer.DataReport", - "JsApiSvr.webview.whitelist", - "trpc.commercial.access.access_sso.SsoAdGet", - "trpc.qpay.homepage2.Homepage2.SsoGetHomepage", - "trpc.qpay.value_added_info.Query.SsoGetPrivilege", - "trpc.qqshop.qgghomepage.Config.SsoGetBottomTab", - "ClubInfoSvc.queryPrivExt", - "OidbSvc.0xcf8", - "LbsSvc.lbs_report", - "OidbSvcTrpcTcp.0x88d_0", - "trpc.down.game_switch.GameSwitch.SsoGetDownloadConfig", - "trpc.zplan.aio_avatar.Mobile.SsoBatchGetSceneConfig", - "OidbSvcTrpcTcp.0x1127_5", - "OidbSvcTrpcTcp.0x10aa", - "WalletConfigSvr.getConfig", - "LightAppSvc.mini_app_userapp.GetDropdownAppList", - "LightAppSvc.mini_app_ad.GetAd", - "SQQzoneSvc.advReport", - "OidbSvc.0xbcb_0", // 内部浏览器URL检测 - //"ConfigurationService.ReqGetConfig" - ) - - override fun invoke(ctx: Context) { - try { - ToServiceMsg::class.java.hookMethod("setRequestSsoSeq").before { - val to = it.thisObject as ToServiceMsg - to.getAttribute("shamrock_seq")?.let { seq -> - it.args[0] = seq - } - } - - val isInit = atomic(false) - CodecWarpper::class.java.hookMethod("init").after { - if (isInit.value) return@after - hookReceive(it.thisObject, it.thisObject.javaClass) - isInit.lazySet(true) - } - CodecWarpper::class.java.hookMethod("nativeOnReceData").before { - if (isInit.value) return@before - hookReceive(it.thisObject, it.thisObject.javaClass) - isInit.lazySet(true) - } - } catch (e: Exception) { - LogCenter.log(e.stackTraceToString(), Level.ERROR) - } - } - - private fun hookReceive(thiz: Any, thizClass: Class<*>) { - val onResponse = thizClass.getDeclaredMethod("onResponse", Integer.TYPE, Any::class.java, Integer.TYPE) - //LogCenter.log("HookWrapperCodec: onResponse = $onResponse", Level.INFO) - DynamicReceiver.register("fake_packet", IPCRequest { - val uin = it.getStringExtra("package_uin")!! - val cmd = it.getStringExtra("package_cmd")!! - val seq = it.getIntExtra("package_seq", 0) - val buffer = it.getByteArrayExtra("package_buffer")!! - //LogCenter.log("伪造收包(cmd = $cmd)") - - val from = FromServiceMsg() - from.requestSsoSeq = seq - from.putWupBuffer(buffer) - from.serviceCmd = cmd - from.appId = MAGIC_APP_ID - from.setMsgSuccess() - from.uin = uin - from.appSeq = seq - onResponse.invoke(thiz, 0, from, 0) - }) - thizClass.hookMethod("onResponse").before { - val from = it.args[1] as FromServiceMsg - try { - if ("SSO.LoginMerge" == from.serviceCmd) { - val merge = SSOLoginMerge.BusiBuffData() - .mergeFrom(from.wupBuffer.slice(4)) - val busiBufVec = merge.BusiBuffVec.get() - busiBufVec.forEach { item -> - if (item.ServiceCmd.get() in IgnoredCmd && ShamrockConfig.isInjectPacket()) { - busiBufVec.remove(item) - } else { - pushOnReceive(FromServiceMsg().apply { - this.requestSsoSeq = item.SeqNo.get() - this.serviceCmd = item.ServiceCmd.get() - putWupBuffer(item.BusiBuff.get().toByteArray()) - }) - } - } - merge.BusiBuffVec.set(busiBufVec) - from.putWupBuffer(merge.toByteArray()) - } else if (from.appId != MAGIC_APP_ID) { - if (from.serviceCmd in IgnoredCmd && ShamrockConfig.isInjectPacket()) { - //from.serviceCmd = "ShamrockInjectedCmd_${from.serviceCmd}" - from.setBusinessFail(1) - from.putWupBuffer(EMPTY_BYTE_ARRAY) - } else if (from.serviceCmd.startsWith("trpc.o3.") && ShamrockConfig.isInjectPacket()) { - from.setBusinessFail(1) - from.putWupBuffer(EMPTY_BYTE_ARRAY) - } else { - pushOnReceive(from) - } - } - } finally { - it.args[1] = from - } - } - } - - private fun pushOnReceive(fromServiceMsg: FromServiceMsg) { - PacketReceiver.internalOnReceive(fromServiceMsg) - } -} - - - diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/IAction.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/IAction.kt deleted file mode 100644 index c5d1f5b5..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/IAction.kt +++ /dev/null @@ -1,9 +0,0 @@ -package moe.fuqiuluo.shamrock.xposed.hooks - -import android.content.Context - -internal interface IAction { - - operator fun invoke(ctx: Context) - -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/InitRemoteService.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/InitRemoteService.kt deleted file mode 100644 index 3de2ca2b..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/InitRemoteService.kt +++ /dev/null @@ -1,133 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) - -package moe.fuqiuluo.shamrock.xposed.hooks - -import android.content.Context -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import moe.fuqiuluo.shamrock.remote.service.WebSocketClientService -import moe.fuqiuluo.shamrock.remote.service.WebSocketService -import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.remote.HTTPServer -import moe.fuqiuluo.shamrock.remote.service.HttpService -import moe.fuqiuluo.shamrock.tools.ShamrockVersion -import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher -import moe.fuqiuluo.symbols.Process -import moe.fuqiuluo.symbols.XposedHook -import mqq.app.MobileQQ -import kotlin.concurrent.timer -import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds - -@XposedHook(Process.MAIN, priority = 10) -internal class InitRemoteService : IAction { - override fun invoke(ctx: Context) { - GlobalScope.launch { - try { - HTTPServer.start(ShamrockConfig.getPort()) - } catch (e: Throwable) { - LogCenter.log(e.stackTraceToString(), Level.ERROR) - } - } - - if (!PlatformUtils.isMqqPackage()) return - - - if (ShamrockConfig.allowWebHook()) { - HttpService.init() - } - - val runtime = AppRuntimeFetcher.appRuntime - if (!runtime.isLogin) { - LogCenter.log("未登录,不启动任何WebSocket服务,登录完成后,请重新启动QQ。", Level.WARN) - return - } - if (ShamrockConfig.openWebSocket()) { - startWebSocketServer() - } - - if (ShamrockConfig.openWebSocketClient()) { - val curUin = runtime.currentAccountUin - val defaultToken = ShamrockConfig.getToken() - ShamrockConfig.getWebSocketClientAddress().forEach { conn -> - if (!conn.address.isNullOrBlank()) { - val token = conn.token ?: defaultToken - val wsHeaders = hashMapOf( - "X-Client-Role" to "Universal", - "X-Self-ID" to curUin, - "User-Agent" to "Shamrock/$ShamrockVersion", - "X-QQ-Version" to PlatformUtils.getClientVersion(MobileQQ.getContext()), - "X-OneBot-Version" to "11", - "X-Impl" to "Shamrock", - "Sec-WebSocket-Protocol" to "11.Shamrock" - ) - if (token.isNotBlank()) { - wsHeaders["authorization"] = "bearer $token" - } - LogCenter.log("尝试连接WebSocketClient(url = ${conn.address})",Level.WARN) - startWebSocketClient(conn.address, conn.heartbeatInterval ?: 15000, wsHeaders) - } - } - } else { - LogCenter.log("未启用被动WebSocket,不会加载连接。") - } - } - - private fun startWebSocketServer() { - GlobalScope.launch { - try { - val config = ShamrockConfig.getActiveWebSocketConfig() ?: return@launch - config.address ?: kotlin.run { - LogCenter.log("WebSocketServer地址不合法", Level.ERROR) - return@launch - } - config.port ?: kotlin.run { - LogCenter.log("WebSocketServer端口不合法", Level.ERROR) - return@launch - } - require(config.port in 0 .. 65536) { "WebSocketServer端口不合法" } - val server = WebSocketService(config.address, config.port!!, config.heartbeatInterval ?: (15 * 1000)) - server.isReuseAddr = true - server.start() - } catch (e: Throwable) { - LogCenter.log(e.stackTraceToString(), Level.ERROR) - } - } - } - - private fun startWebSocketClient( - url: String, - interval: Long, - wsHeaders: HashMap - ) { - GlobalScope.launch { - try { - if (url.startsWith("ws://") || url.startsWith("wss://")) { - val wsClient = WebSocketClientService(url, interval, wsHeaders) - wsClient.connect() - wsClient.launch { - while (true) { - delay(5.seconds) - if (wsClient.isClosed || wsClient.isClosing) { - wsClient.reconnect() - } - } - } - } else { - LogCenter.log("被动WebSocket地址不合法: $url", Level.ERROR) - } - } catch (e: Throwable) { - if (e is RuntimeException) { - LogCenter.log(e.message ?: e.stackTraceToString(), Level.ERROR) - } else { - LogCenter.log(e.stackTraceToString(), Level.ERROR) - } - } - } - } -} diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/IpcService.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/IpcService.kt deleted file mode 100644 index 8777ee17..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/IpcService.kt +++ /dev/null @@ -1,44 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) - -package moe.fuqiuluo.shamrock.xposed.hooks - -import android.content.Context -import android.os.Bundle -import kotlinx.coroutines.DelicateCoroutinesApi -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import moe.fuqiuluo.shamrock.tools.broadcast -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.xposed.helper.internal.* -import moe.fuqiuluo.shamrock.xposed.ipc.ShamrockIpc -import moe.fuqiuluo.symbols.Process -import moe.fuqiuluo.symbols.XposedHook - -@XposedHook(Process.MSF, priority = 0) -internal class IpcService: IAction { - override fun invoke(ctx: Context) { - if (!PlatformUtils.isMsfProcess()) return - initIPCFetcher(ctx) - } - - private fun initIPCFetcher(ctx: Context) { - LogCenter.log("IPC服务已启动:$ctx", Level.INFO) - DynamicReceiver.register("fetch_ipc", IPCRequest { - val name = it.getStringExtra("ipc_name") - LogCenter.log("IPC FETCH => $name,请注意是否泄露了API?") - GlobalScope.launch { - ShamrockIpc.get(name)?.also { binder -> - ctx.broadcast("xqbot") { - putExtra("__cmd", "ipc_callback") - putExtra("ipc", Bundle().also { - it.putString("name", name) - it.putBinder("binder", binder) - }) - } - } ?: LogCenter.log("无法获取IPC: $name", Level.WARN) - } - }) - } -} diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/ListenShamrockUpdate.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/ListenShamrockUpdate.kt deleted file mode 100644 index 23d9fc11..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/ListenShamrockUpdate.kt +++ /dev/null @@ -1,40 +0,0 @@ -package moe.fuqiuluo.shamrock.xposed.hooks - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.os.Process -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.symbols.XposedHook -import kotlin.system.exitProcess - -@XposedHook(priority = 20) -internal class ListenShamrockUpdate: IAction { - override fun invoke(ctx: Context) { - val intent = IntentFilter() - intent.addAction("android.intent.action.PACKAGE_ADDED") - intent.addAction("android.intent.action.PACKAGE_REMOVED") - intent.addAction("android.intent.action.PACKAGE_REPLACED") - intent.addDataScheme("package") - - ctx.registerReceiver(Companion, intent) - } - - companion object: BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - when (intent.action) { - "android.intent.action.PACKAGE_ADDED", - "android.intent.action.PACKAGE_REMOVED", - "android.intent.action.PACKAGE_REPLACED" -> { - val packageName = intent.data?.schemeSpecificPart - if (packageName == "moe.fuqiuluo.shamrock") { - LogCenter.log("Shamrock更新, QQ已经自我销毁。") - Process.killProcess(Process.myPid()) - exitProcess(0) - } - } - } - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/NoBackGround.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/NoBackGround.kt deleted file mode 100644 index a3cd7415..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/NoBackGround.kt +++ /dev/null @@ -1,34 +0,0 @@ -package moe.fuqiuluo.shamrock.xposed.hooks - -import android.content.Context -import de.robv.android.xposed.XposedHelpers -import moe.fuqiuluo.shamrock.tools.hookMethod -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader -import moe.fuqiuluo.symbols.XposedHook -import mqq.app.MobileQQ - -@XposedHook(priority = 10) -internal class NoBackGround: IAction { - override fun invoke(ctx: Context) { - kotlin.runCatching { - XposedHelpers.findClass("com.tencent.mobileqq.activity.miniaio.MiniMsgUser", LuoClassloader) - }.onSuccess { - it.hookMethod("onBackground").before { - it.result = null - } - }.onFailure { - LogCenter.log("Keeping MiniMsgUser alive failed: ${it.message}", Level.WARN) - } - - try { - val application = MobileQQ.getMobileQQ() - application.javaClass.hookMethod("onActivityFocusChanged").before { - it.args[1] = true - } - } catch (e: Throwable) { - LogCenter.log("Keeping MSF alive failed: ${e.message}", Level.WARN) - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/PullConfig.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/PullConfig.kt deleted file mode 100644 index f2f522c6..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/hooks/PullConfig.kt +++ /dev/null @@ -1,95 +0,0 @@ -@file:OptIn(DelicateCoroutinesApi::class) - -package moe.fuqiuluo.shamrock.xposed.hooks - -import android.content.Context -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import moe.fuqiuluo.shamrock.remote.HTTPServer -import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import moe.fuqiuluo.shamrock.xposed.helper.internal.DataRequester -import moe.fuqiuluo.shamrock.xposed.helper.internal.DynamicReceiver -import moe.fuqiuluo.shamrock.xposed.helper.internal.IPCRequest -import moe.fuqiuluo.shamrock.xposed.loader.NativeLoader -import moe.fuqiuluo.symbols.Process -import moe.fuqiuluo.symbols.XposedHook -import mqq.app.MobileQQ -import kotlin.concurrent.thread -import kotlin.system.exitProcess - -@XposedHook(Process.MAIN, priority = 1) -class PullConfig: IAction { - companion object { - @JvmStatic - var isConfigOk = false - } - - private external fun testNativeLibrary(): String - - override fun invoke(ctx: Context) { - if (!PlatformUtils.isMainProcess()) return - - GlobalScope.launch(Dispatchers.Default) { - DynamicReceiver.register("fetchPort", IPCRequest { - DataRequester.request("success", values = mapOf( - "port" to HTTPServer.currServerPort, - "voice" to NativeLoader.isVoiceLoaded - )) - }) - DynamicReceiver.register("checkAndStartService", IPCRequest { - if (HTTPServer.isServiceStarted) { - HTTPServer.isServiceStarted = false - } - initAppService(MobileQQ.getContext()) - }) - DynamicReceiver.register("push_config", IPCRequest { - ctx.toast("动态推送配置文件成功。") - ShamrockConfig.updateConfig(it) - }) - DynamicReceiver.register("change_port", IPCRequest { - when (it.getStringExtra("type")) { - "port" -> { - ctx.toast("动态修改HTTP端口成功。") - HTTPServer.changePort(it.getIntExtra("port", 5700)) - } - "ws_port" -> { - ctx.toast("动态修改WS端口不支持。") - } - "restart" -> { - if(HTTPServer.isServiceStarted) { - ctx.toast("重启HTTPServer完成。") - HTTPServer.restart() - } - } - } - }) - - DataRequester.request("init", onFailure = { - if (!ShamrockConfig.isInit()) { - ctx.toast("请启动Shamrock主进程以初始化服务,进程将退出。") - ShamrockConfig.putDefaultSettings() - thread { - Thread.sleep(3000) - exitProcess(1) - } - } else { - ctx.toast("Shamrock进程未启动,不会推送配置文件。") - initAppService(ctx) - } - }, bodyBuilder = null) { - isConfigOk = true - ShamrockConfig.updateConfig(it) - initAppService(ctx) - } - } - } - - private fun initAppService(ctx: Context) { - NativeLoader.load("shamrock") - ctx.toast(testNativeLibrary()) - runServiceActions(ctx) - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/ipc/ShamrockIpc.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/ipc/ShamrockIpc.kt deleted file mode 100644 index 19f801c2..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/ipc/ShamrockIpc.kt +++ /dev/null @@ -1,45 +0,0 @@ -package moe.fuqiuluo.shamrock.xposed.ipc - -import android.os.IBinder -import moe.fuqiuluo.shamrock.utils.PlatformUtils -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import kotlinx.coroutines.withTimeoutOrNull -import moe.fuqiuluo.shamrock.tools.broadcast -import moe.fuqiuluo.shamrock.xposed.helper.internal.DynamicReceiver -import moe.fuqiuluo.shamrock.xposed.helper.internal.IPCRequest -import moe.fuqiuluo.shamrock.xposed.ipc.bytedata.ByteDataCreator -import moe.fuqiuluo.shamrock.xposed.ipc.qsign.QSignGenerator -import mqq.app.MobileQQ -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine - -internal object ShamrockIpc { - const val IPC_QSIGN = "qsign" - const val IPC_BYTEDATA = "bytedata" - - private val IpcChannel = hashMapOf( - IPC_QSIGN to QSignGenerator, - IPC_BYTEDATA to ByteDataCreator - ) - - suspend fun get(name: String?): IBinder? { - return if (PlatformUtils.isMsfProcess()) { - IpcChannel[name] - } else { - withTimeoutOrNull(3000) { - suspendCoroutine { continuation -> - DynamicReceiver.register("ipc_callback", IPCRequest { - val bundle = it.getBundleExtra("ipc")!! - val binder = bundle.getBinder("binder") - continuation.resume(binder) - }) - MobileQQ.getContext().broadcast("msf") { - putExtra("__cmd", "fetch_ipc") - putExtra("ipc_name", name) - } - } - } - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/ipc/bytedata/ByteData.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/ipc/bytedata/ByteData.kt deleted file mode 100644 index 60fc6b8b..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/ipc/bytedata/ByteData.kt +++ /dev/null @@ -1,41 +0,0 @@ -package moe.fuqiuluo.shamrock.xposed.ipc.bytedata - -import android.os.Parcel -import android.os.Parcelable -import com.tencent.secprotocol.ByteData -import moe.fuqiuluo.shamrock.xposed.ipc.bytedata.IByteData - -data class IByteDataSign( - val sign: ByteArray? -): Parcelable { - constructor(parcel: Parcel) : this( - parcel.createByteArray() - ) - - override fun writeToParcel(parcel: Parcel, flags: Int) { - parcel.writeByteArray(sign) - } - - override fun describeContents(): Int { - return 0 - } - - companion object CREATOR : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): IByteDataSign { - return IByteDataSign(parcel) - } - - override fun newArray(size: Int): Array { - return arrayOfNulls(size) - } - } -} - -internal object ByteDataCreator: IByteData.Stub() { - override fun sign(uin: String?, data: String?, salt: ByteArray?): IByteDataSign { - val byteData = ByteData.getInstance() - byteData.setDataEx(uin, data) - return IByteDataSign(byteData.getSign(uin, data, salt)) - } - -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/ipc/qsign/QSign.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/ipc/qsign/QSign.kt deleted file mode 100644 index 0555f37c..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/ipc/qsign/QSign.kt +++ /dev/null @@ -1,78 +0,0 @@ -package moe.fuqiuluo.shamrock.xposed.ipc.qsign - -import android.os.Parcel -import android.os.Parcelable -import com.tencent.mobileqq.fe.FEKit -import com.tencent.mobileqq.qsec.qsecdandelionsdk.Dandelion -import com.tencent.mobileqq.qsec.qsecurity.QSec -import com.tencent.qphone.base.util.BaseApplication -import moe.fuqiuluo.shamrock.tools.toHexString -import moe.fuqiuluo.shamrock.utils.MMKVFetcher -import moe.fuqiuluo.shamrock.xposed.hooks.GuidLock -import mqq.app.MobileQQ -import oicq.wlogin_sdk.tools.util - -data class IQSign( - val token: ByteArray, - val extra: ByteArray, - val sign: ByteArray -): Parcelable { - constructor(parcel: Parcel) : this( - parcel.createByteArray()!!, - parcel.createByteArray()!!, - parcel.createByteArray()!! - ) - - override fun writeToParcel(parcel: Parcel, flags: Int) { - parcel.writeByteArray(token) - parcel.writeByteArray(extra) - parcel.writeByteArray(sign) - } - - override fun describeContents(): Int { - return 0 - } - - companion object CREATOR : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): IQSign { - return IQSign(parcel) - } - - override fun newArray(size: Int): Array { - return arrayOfNulls(size) - } - } -} - -private fun getFEKit(uin: String): FEKit { - val guildLock = MMKVFetcher.mmkvWithId("guid") - val ctx = BaseApplication.getContext() - val guid = guildLock.getString("guid", util.get_last_guid(ctx).toHexString()) - val fe = FEKit.getInstance() - fe.init( - BaseApplication.getContext(), - uin, - guid, "", GuidLock.qimei, - BaseApplication.getContext().qua - ) - return fe -} - -internal object QSignGenerator: IQSigner.Stub() { - override fun sign(cmd: String, seq: Int, uin: String, buffer: ByteArray): IQSign { - val sign = getFEKit(uin).getSign(cmd, buffer, seq, uin) - return IQSign(sign.token, sign.extra, sign.sign) - } - - override fun energy(module: String, salt: ByteArray): ByteArray { - return Dandelion.getInstance().fly(module, salt) - } - - override fun xwDebugId(uin: String, start: String, end: String): ByteArray { - return QSec.getInstance().getFeKitAttach(MobileQQ.getContext(), uin, start, end) - } - - override fun getCmdWhiteList(): List { - return FEKit.getInstance().cmdWhiteList - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/loader/KeepAlive.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/loader/KeepAlive.kt deleted file mode 100644 index a61a39ad..00000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/xposed/loader/KeepAlive.kt +++ /dev/null @@ -1,174 +0,0 @@ -@file:Suppress("UNUSED_VARIABLE", "LocalVariableName") -package moe.fuqiuluo.shamrock.xposed.loader - -import android.content.pm.ApplicationInfo -import android.os.Build -import android.os.Process -import de.robv.android.xposed.XSharedPreferences -import de.robv.android.xposed.XposedBridge -import de.robv.android.xposed.XposedHelpers -import moe.fuqiuluo.shamrock.tools.hookMethod -import java.lang.reflect.Method -import kotlin.concurrent.timer - -internal object KeepAlive { - private val KeepPackage = arrayOf( - "com.tencent.mobileqq", "moe.fuqiuluo.shamrock" - ) - private val KeepRecords = arrayListOf() - private lateinit var KeepThread: Thread - - private lateinit var METHOD_IS_KILLED: Method - private var allowPersistent: Boolean = false - - operator fun invoke(loader: ClassLoader) { - val pref = XSharedPreferences("moe.fuqiuluo.shamrock", "shared_config") - hookAMS(pref, loader) - hookDoze(pref, loader) - } - - private fun hookDoze(pref: XSharedPreferences, loader: ClassLoader) { - if (pref.file.canRead() && pref.getBoolean("hook_doze", false)) { - val result = runCatching { - val DeviceIdleController = XposedHelpers.findClass("com.android.server.DeviceIdleController", loader) - ?: return@runCatching -1 - val becomeActiveLocked = XposedHelpers.findMethodBestMatch(DeviceIdleController, "becomeActiveLocked", String::class.java, Integer.TYPE) - ?: return@runCatching -2 - if (!becomeActiveLocked.isAccessible) { - becomeActiveLocked.isAccessible = true - } - DeviceIdleController.hookMethod("onStart").after { - XposedBridge.log("[Shamrock] DeviceIdleController onStart") - } - DeviceIdleController.hookMethod("becomeInactiveIfAppropriateLocked").before { - XposedBridge.log("[Shamrock] DeviceIdleController becomeInactiveIfAppropriateLocked") - it.result = Unit - } - DeviceIdleController.hookMethod("stepIdleStateLocked").before { - XposedBridge.log("[Shamrock] DeviceIdleController stepIdleStateLocked") - it.result = Unit - } - return@runCatching 0 - }.getOrElse { -5 } - if(result < 0) { - XposedBridge.log("[Shamrock] Unable to hookDoze: $result") - } - } - } - - private fun hookAMS(pref: XSharedPreferences, loader: ClassLoader) { - kotlin.runCatching { - val ActivityManagerService = XposedHelpers.findClass("com.android.server.am.ActivityManagerService", loader) - ActivityManagerService.hookMethod("newProcessRecordLocked").after { - increaseAdj(it.result) - } - }.onFailure { - XposedBridge.log("[Shamrock] Plan A failed: ${it.message}") - } - - if (pref.file.canRead()) { - allowPersistent = pref.getBoolean("persistent", false) - XposedBridge.log("[Shamrock] allowPersistent = $allowPersistent") - } else { - XposedBridge.log("[Shamrock] unable to load XSharedPreferences") - } - - kotlin.runCatching { - val ProcessList = XposedHelpers.findClass("com.android.server.am.ProcessList", loader) - ProcessList.hookMethod("newProcessRecordLocked").after { - increaseAdj(it.result) - } - }.onFailure { - XposedBridge.log("[Shamrock] Plan B failed: ${it.message}") - } - } - - private fun checkThread() { - if (!::KeepThread.isInitialized || !KeepThread.isAlive) { - KeepThread = Thread { - val deletedList = mutableSetOf() - while (true) { - Thread.sleep(100) - KeepRecords.forEach { - val isKilled = if (::METHOD_IS_KILLED.isInitialized) { - kotlin.runCatching { - METHOD_IS_KILLED.invoke(it) as Boolean - }.getOrElse { false } - } else false - if (isKilled) { - deletedList.add(it) - XposedBridge.log("Process Closed: $it") - } else { - keepByAdj(it) - } - } - if (deletedList.isNotEmpty()) { - KeepRecords.removeAll(deletedList) - deletedList.clear() - } - } - }.also { - it.isDaemon = true - it.start() - } - } - } - - private fun increaseAdj(record: Any) { - if (record.toString().contains("system", ignoreCase = true)) { - return - } - val applicationInfo = record.javaClass.getDeclaredField("info").also { - if (!it.isAccessible) it.isAccessible = true - }.get(record) as ApplicationInfo - if(applicationInfo.processName in KeepPackage) { - XposedBridge.log("[Shamrock] Process is keeping: $record") - KeepRecords.add(record) - keepByAdj(record) - // Error - if (allowPersistent) { - XposedBridge.log("[Shamrock] Open NoDied Mode!!!") - keepByPersistent(record) - } - checkThread() - } - } - - private fun keepByAdj(record: Any) { - val clazz = record.javaClass - if (!::METHOD_IS_KILLED.isInitialized) { - kotlin.runCatching { - METHOD_IS_KILLED = clazz.getDeclaredMethod("isKilled").also { - if (!it.isAccessible) it.isAccessible = true - } - } - } - kotlin.runCatching { - val newState = clazz.getDeclaredField("mState").also { - if (!it.isAccessible) it.isAccessible = true - }.get(record) - val MethodSetMaxAdj = newState.javaClass.getDeclaredMethod("setMaxAdj", Int::class.java).also { - if (!it.isAccessible) it.isAccessible = true - }.invoke(newState, 1) - }.onFailure { - clazz.getDeclaredField("maxAdj").also { - if (!it.isAccessible) it.isAccessible = true - }.set(record, 1) - } - } - - private fun keepByPersistent(record: Any) { - val clazz = record.javaClass - kotlin.runCatching { - if (Build.VERSION.SDK_INT < 29) { - clazz.getDeclaredField("persistent").also { - if (!it.isAccessible) it.isAccessible = true - }.set(record, true) - } else { - clazz.getDeclaredMethod("setPersistent", Boolean::class.java).also { - if (!it.isAccessible) it.isAccessible = true - }.invoke(record, true) - } - } - } -} \ No newline at end of file