mirror of
https://github.com/whitechi73/OpenShamrock.git
synced 2024-08-14 13:12:17 +08:00
Merge branch 'master' of github.com:whitechi73/OpenShamrock
This commit is contained in:
commit
69bc80e9b3
@ -18,6 +18,8 @@
|
|||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.Shamrock"
|
android:theme="@style/Theme.Shamrock"
|
||||||
android:zygotePreloadName="@string/app_name"
|
android:zygotePreloadName="@string/app_name"
|
||||||
|
android:multiArch="true"
|
||||||
|
android:extractNativeLibs="true"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
|
@ -224,6 +224,16 @@ object ShamrockConfig {
|
|||||||
preferences.edit().putBoolean("debug", v).apply()
|
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 setAntiTrace(ctx: Context, v: Boolean) {
|
||||||
|
val preferences = ctx.getSharedPreferences("config", 0)
|
||||||
|
preferences.edit().putBoolean("anti_qq_trace", v).apply()
|
||||||
|
}
|
||||||
|
|
||||||
fun isInjectPacket(ctx: Context): Boolean {
|
fun isInjectPacket(ctx: Context): Boolean {
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
val preferences = ctx.getSharedPreferences("config", 0)
|
||||||
return preferences.getBoolean("inject_packet", false)
|
return preferences.getBoolean("inject_packet", false)
|
||||||
@ -293,6 +303,7 @@ object ShamrockConfig {
|
|||||||
"ssl_pwd" to preferences.getString("ssl_pwd", ""),
|
"ssl_pwd" to preferences.getString("ssl_pwd", ""),
|
||||||
"inject_packet" to preferences.getBoolean("inject_packet", false),
|
"inject_packet" to preferences.getBoolean("inject_packet", false),
|
||||||
"debug" to preferences.getBoolean("debug", false),
|
"debug" to preferences.getBoolean("debug", false),
|
||||||
|
"anti_qq_trace" to preferences.getBoolean("anti_qq_trace", true),
|
||||||
"auto_clear" to preferences.getBoolean("auto_clear", false),
|
"auto_clear" to preferences.getBoolean("auto_clear", false),
|
||||||
"ssl_private_pwd" to preferences.getString("ssl_private_pwd", ""),
|
"ssl_private_pwd" to preferences.getString("ssl_private_pwd", ""),
|
||||||
"key_store" to preferences.getString("key_store", ""),
|
"key_store" to preferences.getString("key_store", ""),
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package moe.fuqiuluo.shamrock.ui.fragment
|
package moe.fuqiuluo.shamrock.ui.fragment
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.absolutePadding
|
import androidx.compose.foundation.layout.absolutePadding
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
@ -85,6 +84,17 @@ fun LabFragment() {
|
|||||||
ShamrockConfig.pushUpdate(ctx)
|
ShamrockConfig.pushUpdate(ctx)
|
||||||
return@Function true
|
return@Function true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Function(
|
||||||
|
title = "防止调用栈检测",
|
||||||
|
desc = "防止QQ进行堆栈跟踪检测,需要重新启动QQ。",
|
||||||
|
descColor = it,
|
||||||
|
isSwitch = ShamrockConfig.isAntiTrace(ctx)
|
||||||
|
) {
|
||||||
|
ShamrockConfig.setAntiTrace(ctx, it)
|
||||||
|
ShamrockConfig.pushUpdate(ctx)
|
||||||
|
return@Function true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ public class MMKV implements SharedPreferences, SharedPreferences.Editor {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SharedPreferences.Editor putBoolean(String str, boolean z) {
|
public SharedPreferences.Editor putBoolean(String s, boolean z) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,5 +96,11 @@ dependencies {
|
|||||||
//ksp("androidx.room:room-compiler:$roomVersion")
|
//ksp("androidx.room:room-compiler:$roomVersion")
|
||||||
// optional - Kotlin Extensions and Coroutines support for Room
|
// optional - Kotlin Extensions and Coroutines support for Room
|
||||||
implementation("androidx.room:room-ktx:$roomVersion")
|
implementation("androidx.room:room-ktx:$roomVersion")
|
||||||
|
|
||||||
|
testImplementation("junit:junit:4.13.2")
|
||||||
|
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||||
|
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||||
|
androidTestImplementation(platform("androidx.compose:compose-bom:2023.06.01"))
|
||||||
|
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1
xposed/src/main/assets/native_init
Normal file
1
xposed/src/main/assets/native_init
Normal file
@ -0,0 +1 @@
|
|||||||
|
libclover.so
|
@ -6,11 +6,14 @@
|
|||||||
# Sets the minimum CMake version required for this project.
|
# Sets the minimum CMake version required for this project.
|
||||||
cmake_minimum_required(VERSION 3.22.1)
|
cmake_minimum_required(VERSION 3.22.1)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
|
# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
|
||||||
# Since this is the top level CMakeLists.txt, the project name is also accessible
|
# Since this is the top level CMakeLists.txt, the project name is also accessible
|
||||||
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
|
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
|
||||||
# build script scope).
|
# build script scope).
|
||||||
project("xposed")
|
project("clover")
|
||||||
|
|
||||||
# Creates and names a library, sets it as either STATIC
|
# Creates and names a library, sets it as either STATIC
|
||||||
# or SHARED, and provides the relative paths to its source code.
|
# or SHARED, and provides the relative paths to its source code.
|
||||||
@ -27,7 +30,7 @@ project("xposed")
|
|||||||
# used in the AndroidManifest.xml file.
|
# used in the AndroidManifest.xml file.
|
||||||
add_library(${CMAKE_PROJECT_NAME} SHARED
|
add_library(${CMAKE_PROJECT_NAME} SHARED
|
||||||
# List C/C++ source files with relative paths to this CMakeLists.txt.
|
# List C/C++ source files with relative paths to this CMakeLists.txt.
|
||||||
xposed.cpp)
|
clover.cpp)
|
||||||
|
|
||||||
# Specifies libraries CMake should link to your target library. You
|
# Specifies libraries CMake should link to your target library. You
|
||||||
# can link libraries from various origins, such as libraries defined in this
|
# can link libraries from various origins, such as libraries defined in this
|
||||||
|
116
xposed/src/main/cpp/clover.cpp
Normal file
116
xposed/src/main/cpp/clover.cpp
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
#include <jni.h>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <initializer_list>
|
||||||
|
#include <vector>
|
||||||
|
#include <sys/auxv.h>
|
||||||
|
#include <android/log.h>
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#include <string_view>
|
||||||
|
#include "lsposed.h"
|
||||||
|
#include "jnihelper.h"
|
||||||
|
|
||||||
|
static HookFunType hook_function = nullptr;
|
||||||
|
|
||||||
|
static std::vector<std::string> qemu_detect_props = {
|
||||||
|
"init.svc.qemu-props", "qemu.hw.mainkeys", "qemu.sf.fake_camera", "ro.kernel.android.qemud",
|
||||||
|
"qemu.sf.lcd_density", "init.svc.qemud", "ro.kernel.qemu",
|
||||||
|
"libc.debug.malloc"
|
||||||
|
};
|
||||||
|
|
||||||
|
int (*backup_system_property_get)(const char *name, char *value);
|
||||||
|
|
||||||
|
int fake_system_property_get(const char *name, char *value) {
|
||||||
|
for (auto &prop: qemu_detect_props) {
|
||||||
|
if (strstr(name, prop.c_str())) {
|
||||||
|
LOGI("[Shamrock] bypass qemu detection");
|
||||||
|
value[0] = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strstr(name, "ro.debuggable")
|
||||||
|
|| strstr(name, "ro.kernel.qemu.gles")
|
||||||
|
|| strstr(name, "debug.atrace.tags.enableflags")) {
|
||||||
|
strcpy(value, "0");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strstr(name, "ro.product.cpu.abilist")) {
|
||||||
|
int len = backup_system_property_get(name, value);
|
||||||
|
if (len > 0) {
|
||||||
|
if (strstr(value, "x86")) {
|
||||||
|
strcpy(value, "arm64-v8a,armeabi-v7a,armeabi");
|
||||||
|
return 29;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strstr(name, "ro.hardware")) {
|
||||||
|
int len = backup_system_property_get(name, value);
|
||||||
|
if (len > 0) {
|
||||||
|
if (strstr(value, "generic")
|
||||||
|
|| strstr(value, "unknown")
|
||||||
|
|| strstr(value, "emulator")
|
||||||
|
|| strstr(value, "vbox")
|
||||||
|
|| strstr(value, "genymotion")
|
||||||
|
|| strstr(value, "goldfish")) {
|
||||||
|
strcpy(value, "qcom");
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
//LOGI("[Shamrock] fake_system_property_get(%s)", name);
|
||||||
|
return backup_system_property_get(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_library_loaded(const char *name, void *handle) {
|
||||||
|
auto libraryName = std::string(name);
|
||||||
|
if (libraryName.ends_with("libc.so") || libraryName.ends_with("libfekit.so")) {
|
||||||
|
void *target = dlsym(handle, "__system_property_get");
|
||||||
|
if (target != nullptr) {
|
||||||
|
hook_function(target, (void *)fake_system_property_get, (void **) &backup_system_property_get);
|
||||||
|
} else {
|
||||||
|
LOGE("[Shamrock] failed to hook __system_property_get");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" [[gnu::visibility("default")]] [[gnu::used]]
|
||||||
|
jint JNI_OnLoad(JavaVM *jvm, void*) {
|
||||||
|
JNIHelper::initJavaVM(jvm);
|
||||||
|
int attach = 0;
|
||||||
|
JNIEnv *env = JNIHelper::getJNIEnv(&attach);
|
||||||
|
|
||||||
|
// do something
|
||||||
|
LOGI("[Shamrock] JNI_OnLoad NativeModule Init: %p", env);
|
||||||
|
|
||||||
|
if (attach == 1) {
|
||||||
|
JNIHelper::delJNIEnv();
|
||||||
|
}
|
||||||
|
|
||||||
|
//hook_function((void *)env->functions->FindClass, (void *)fake_FindClass, (void **)&backup_FindClass);
|
||||||
|
return JNI_VERSION_1_6;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" [[gnu::visibility("default")]] [[gnu::used]]
|
||||||
|
NativeOnModuleLoaded native_init(const NativeAPIEntries *entries) {
|
||||||
|
hook_function = entries->hook_func;
|
||||||
|
LOGI("[Shamrock] LSPosed NativeModule Init: %p", hook_function);
|
||||||
|
return on_library_loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT jboolean JNICALL
|
||||||
|
Java_moe_fuqiuluo_shamrock_xposed_XposedEntry_00024Companion_injected(JNIEnv *env, jobject thiz) {
|
||||||
|
return hook_function != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT jboolean JNICALL
|
||||||
|
Java_moe_fuqiuluo_shamrock_xposed_XposedEntry_00024Companion_hasEnv(JNIEnv *env, jobject thiz) {
|
||||||
|
return JNIHelper::global_jvm != nullptr;
|
||||||
|
}
|
40
xposed/src/main/cpp/jnihelper.h
Normal file
40
xposed/src/main/cpp/jnihelper.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#ifndef SHAMROCK_JNIHELPER_H
|
||||||
|
#define SHAMROCK_JNIHELPER_H
|
||||||
|
|
||||||
|
#include "android/log.h"
|
||||||
|
|
||||||
|
namespace JNIHelper {
|
||||||
|
static JavaVM *global_jvm = nullptr;
|
||||||
|
|
||||||
|
void initJavaVM(JavaVM *jvm) {
|
||||||
|
global_jvm = jvm;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEnv *getJNIEnv(int *attach) {
|
||||||
|
if (global_jvm == NULL) return NULL;
|
||||||
|
|
||||||
|
*attach = 0;
|
||||||
|
JNIEnv *jni_env = NULL;
|
||||||
|
|
||||||
|
int status = global_jvm->GetEnv((void **)&jni_env, JNI_VERSION_1_6);
|
||||||
|
|
||||||
|
if (status == JNI_EDETACHED || jni_env == NULL) {
|
||||||
|
status = global_jvm->AttachCurrentThread(&jni_env, NULL);
|
||||||
|
if (status < 0) {
|
||||||
|
jni_env = NULL;
|
||||||
|
} else {
|
||||||
|
*attach = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return jni_env;
|
||||||
|
}
|
||||||
|
|
||||||
|
jint delJNIEnv() {
|
||||||
|
if (global_jvm == nullptr) return 0;
|
||||||
|
return global_jvm->DetachCurrentThread();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif //SHAMROCK_JNIHELPER_H
|
27
xposed/src/main/cpp/lsposed.h
Normal file
27
xposed/src/main/cpp/lsposed.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#ifndef SHAMROCK_LSPOSED_H
|
||||||
|
#define SHAMROCK_LSPOSED_H
|
||||||
|
|
||||||
|
#include "stdint.h"
|
||||||
|
|
||||||
|
#define TAG "LSPosed-Bridge"
|
||||||
|
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__)
|
||||||
|
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__)
|
||||||
|
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__)
|
||||||
|
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__)
|
||||||
|
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__)
|
||||||
|
|
||||||
|
typedef int (*HookFunType)(void *func, void *replace, void **backup);
|
||||||
|
|
||||||
|
typedef int (*UnhookFunType)(void *func);
|
||||||
|
|
||||||
|
typedef void (*NativeOnModuleLoaded)(const char *name, void *handle);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t version;
|
||||||
|
HookFunType hook_func;
|
||||||
|
UnhookFunType unhook_func;
|
||||||
|
} NativeAPIEntries;
|
||||||
|
|
||||||
|
typedef NativeOnModuleLoaded (*NativeInit)(const NativeAPIEntries *entries);
|
||||||
|
|
||||||
|
#endif //SHAMROCK_LSPOSED_H
|
@ -1,5 +0,0 @@
|
|||||||
#include <jni.h>
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
#include <sys/auxv.h>
|
|
||||||
|
|
@ -318,7 +318,7 @@ internal object GroupSvc: BaseSvc() {
|
|||||||
|
|
||||||
fun getOwner(groupId: String): Long {
|
fun getOwner(groupId: String): Long {
|
||||||
val groupInfo = getGroupInfo(groupId)
|
val groupInfo = getGroupInfo(groupId)
|
||||||
return groupInfo.troopowneruin.toLong()
|
return groupInfo.troopowneruin?.toLong() ?: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isOwner(groupId: String): Boolean {
|
fun isOwner(groupId: String): Boolean {
|
||||||
|
@ -96,9 +96,21 @@ internal object MessageMaker {
|
|||||||
"touch" to MessageMaker::createTouchElem,
|
"touch" to MessageMaker::createTouchElem,
|
||||||
"weather" to MessageMaker::createWeatherElem,
|
"weather" to MessageMaker::createWeatherElem,
|
||||||
"json" to MessageMaker::createJsonElem,
|
"json" to MessageMaker::createJsonElem,
|
||||||
|
//"node" to MessageMaker::createNodeElem,
|
||||||
//"multi_msg" to MessageMaker::createLongMsgStruct,
|
//"multi_msg" to MessageMaker::createLongMsgStruct,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// private suspend fun createNodeElem(
|
||||||
|
// chatType: Int,
|
||||||
|
// msgId: Long,
|
||||||
|
// peerId: String,
|
||||||
|
// data: JsonObject
|
||||||
|
// ): Result<MsgElement> {
|
||||||
|
// data.checkAndThrow("data")
|
||||||
|
// SendForwardMessage(MsgConstant.KCHATTYPEC2C, TicketSvc.getUin(), data["content"].asJsonArray)
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
private suspend fun createJsonElem(
|
private suspend fun createJsonElem(
|
||||||
chatType: Int,
|
chatType: Int,
|
||||||
msgId: Long,
|
msgId: Long,
|
||||||
|
@ -51,8 +51,29 @@ internal object LogCenter {
|
|||||||
|
|
||||||
private val format = SimpleDateFormat("[HH:mm:ss] ")
|
private val format = SimpleDateFormat("[HH:mm:ss] ")
|
||||||
|
|
||||||
fun log(string: String, level: Level = Level.INFO, toast: Boolean = false) =
|
fun log(string: String, level: Level = Level.INFO, toast: Boolean = false) {
|
||||||
log({ string }, level, toast)
|
if (!ShamrockConfig.isDebug() && level == Level.DEBUG) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toast) {
|
||||||
|
MobileQQ.getContext().toast(string)
|
||||||
|
}
|
||||||
|
// 把日志广播到主进程
|
||||||
|
GlobalScope.launch(Dispatchers.Default) {
|
||||||
|
DataRequester.request("send_message", bodyBuilder = {
|
||||||
|
put("string", string)
|
||||||
|
put("level", level.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!LogFile.exists()) {
|
||||||
|
LogFile.createNewFile()
|
||||||
|
}
|
||||||
|
val format = "%s%s %s\n".format(format.format(Date()), level.name, string)
|
||||||
|
|
||||||
|
LogFile.appendText(format)
|
||||||
|
}
|
||||||
|
|
||||||
fun log(
|
fun log(
|
||||||
string: () -> String,
|
string: () -> String,
|
||||||
|
@ -74,7 +74,7 @@ internal object MessageHelper {
|
|||||||
val nonMsg: Boolean = message.isEmpty()
|
val nonMsg: Boolean = message.isEmpty()
|
||||||
return if (!nonMsg) {
|
return if (!nonMsg) {
|
||||||
val service = QRoute.api(IMsgService::class.java)
|
val service = QRoute.api(IMsgService::class.java)
|
||||||
if(callback is MsgSvc.MessageCallback) {
|
if (callback is MsgSvc.MessageCallback) {
|
||||||
callback.msgHash = uniseq.first
|
callback.msgHash = uniseq.first
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ internal object MessageHelper {
|
|||||||
val nonMsg: Boolean = message.isEmpty()
|
val nonMsg: Boolean = message.isEmpty()
|
||||||
return if (!nonMsg) {
|
return if (!nonMsg) {
|
||||||
val service = QRoute.api(IMsgService::class.java)
|
val service = QRoute.api(IMsgService::class.java)
|
||||||
if(callback is MsgSvc.MessageCallback) {
|
if (callback is MsgSvc.MessageCallback) {
|
||||||
callback.msgHash = uniseq.first
|
callback.msgHash = uniseq.first
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +132,7 @@ internal object MessageHelper {
|
|||||||
val nonMsg: Boolean = message.isEmpty()
|
val nonMsg: Boolean = message.isEmpty()
|
||||||
return if (!nonMsg) {
|
return if (!nonMsg) {
|
||||||
val service = QRoute.api(IMsgService::class.java)
|
val service = QRoute.api(IMsgService::class.java)
|
||||||
if(callback is MsgSvc.MessageCallback) {
|
if (callback is MsgSvc.MessageCallback) {
|
||||||
callback.msgHash = uniseq.first
|
callback.msgHash = uniseq.first
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,7 +156,7 @@ internal object MessageHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun obtainMessageTypeByDetailType(detailType: String): Int {
|
fun obtainMessageTypeByDetailType(detailType: String): Int {
|
||||||
return when(detailType) {
|
return when (detailType) {
|
||||||
"troop", "group" -> MsgConstant.KCHATTYPEGROUP
|
"troop", "group" -> MsgConstant.KCHATTYPEGROUP
|
||||||
"private" -> MsgConstant.KCHATTYPEC2C
|
"private" -> MsgConstant.KCHATTYPEC2C
|
||||||
"less" -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
|
"less" -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN
|
||||||
@ -166,7 +166,7 @@ internal object MessageHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun obtainDetailTypeByMsgType(msgType: Int): String {
|
fun obtainDetailTypeByMsgType(msgType: Int): String {
|
||||||
return when(msgType) {
|
return when (msgType) {
|
||||||
MsgConstant.KCHATTYPEGROUP -> "group"
|
MsgConstant.KCHATTYPEGROUP -> "group"
|
||||||
MsgConstant.KCHATTYPEC2C -> "private"
|
MsgConstant.KCHATTYPEC2C -> "private"
|
||||||
MsgConstant.KCHATTYPEGUILD -> "guild"
|
MsgConstant.KCHATTYPEGUILD -> "guild"
|
||||||
@ -180,9 +180,9 @@ internal object MessageHelper {
|
|||||||
var hasActionMsg = false
|
var hasActionMsg = false
|
||||||
messageList.forEach {
|
messageList.forEach {
|
||||||
val msg = it.jsonObject
|
val msg = it.jsonObject
|
||||||
try {
|
val maker = MessageMaker[msg["type"].asString]
|
||||||
val maker = MessageMaker[msg["type"].asString]
|
if (maker != null) {
|
||||||
if (maker != null) {
|
try {
|
||||||
val data = msg["data"].asJsonObjectOrNull ?: EmptyJsonObject
|
val data = msg["data"].asJsonObjectOrNull ?: EmptyJsonObject
|
||||||
maker(chatType, msgId, targetUin, data).onSuccess { msgElem ->
|
maker(chatType, msgId, targetUin, data).onSuccess { msgElem ->
|
||||||
msgList.add(msgElem)
|
msgList.add(msgElem)
|
||||||
@ -193,18 +193,19 @@ internal object MessageHelper {
|
|||||||
hasActionMsg = true
|
hasActionMsg = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} catch (e: Throwable) {
|
||||||
LogCenter.log("不支持的消息类型: ${msg["type"].asString}", Level.ERROR)
|
LogCenter.log(e.stackTraceToString(), Level.ERROR)
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} else {
|
||||||
LogCenter.log(e.stackTraceToString(), Level.ERROR)
|
LogCenter.log("不支持的消息类型: ${msg["type"].asString}", Level.ERROR)
|
||||||
|
return false to arrayListOf()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return hasActionMsg to msgList
|
return hasActionMsg to msgList
|
||||||
}
|
}
|
||||||
|
|
||||||
fun generateMsgIdHash(chatType: Int, msgId: Long): Int {
|
fun generateMsgIdHash(chatType: Int, msgId: Long): Int {
|
||||||
val key = when (chatType) {
|
val key = when (chatType) {
|
||||||
MsgConstant.KCHATTYPEGROUP -> "grp$msgId"
|
MsgConstant.KCHATTYPEGROUP -> "grp$msgId"
|
||||||
MsgConstant.KCHATTYPEC2C -> "c2c$msgId"
|
MsgConstant.KCHATTYPEC2C -> "c2c$msgId"
|
||||||
MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> "tmpgrp$msgId"
|
MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> "tmpgrp$msgId"
|
||||||
|
@ -34,8 +34,8 @@ internal object ActionManager {
|
|||||||
GetGroupSystemMsg, GetProhibitedMemberList, GetEssenceMessageList, GetGroupNotice, SendGroupNotice,
|
GetGroupSystemMsg, GetProhibitedMemberList, GetEssenceMessageList, GetGroupNotice, SendGroupNotice,
|
||||||
|
|
||||||
// MSG ACTIONS
|
// MSG ACTIONS
|
||||||
SendMessage, DeleteMessage, GetMsg, GetForwardMsg, SendGroupForwardMsg, SendGroupMessage, SendPrivateMessage,
|
SendMessage, DeleteMessage, GetMsg, GetForwardMsg, SendPrivateForwardMessage, SendGroupMessage, SendPrivateMessage,
|
||||||
ClearMsgs, GetHistoryMsg, GetGroupMsgHistory, SendPrivateForwardMsg,
|
ClearMsgs, GetHistoryMsg, GetGroupMsgHistory, SendGroupForwardMessage,
|
||||||
|
|
||||||
// RESOURCE ACTION
|
// RESOURCE ACTION
|
||||||
GetRecord, GetImage, UploadGroupFile, CreateGroupFileFolder, DeleteGroupFolder,
|
GetRecord, GetImage, UploadGroupFile, CreateGroupFileFolder, DeleteGroupFolder,
|
||||||
|
@ -0,0 +1,169 @@
|
|||||||
|
package moe.fuqiuluo.shamrock.remote.action.handlers
|
||||||
|
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MultiMsgInfo
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.serialization.json.JsonArray
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.msg.convert.toSegments
|
||||||
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
|
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.ForwardMessageResult
|
||||||
|
import moe.fuqiuluo.shamrock.tools.*
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
|
sealed interface ForwardMsgNode {
|
||||||
|
class MessageIdNode(
|
||||||
|
val id: Int
|
||||||
|
) : ForwardMsgNode
|
||||||
|
|
||||||
|
open class MessageNode(
|
||||||
|
val name: String,
|
||||||
|
val content: JsonElement?
|
||||||
|
) : ForwardMsgNode
|
||||||
|
|
||||||
|
object EmptyNode : MessageNode("", null)
|
||||||
|
}
|
||||||
|
|
||||||
|
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")) {
|
||||||
|
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 -> session.getStringOrNull("user_id") ?: return noParam(
|
||||||
|
"user_id",
|
||||||
|
session.echo
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> error("unknown chat type: $chatType")
|
||||||
|
}
|
||||||
|
if (session.isArray("messages")) {
|
||||||
|
val messages = session.getArray("messages")
|
||||||
|
invoke(chatType, peerId, messages, echo = session.echo)
|
||||||
|
}
|
||||||
|
return 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,
|
||||||
|
message: JsonArray,
|
||||||
|
echo: JsonElement = EmptyJsonString
|
||||||
|
): String {
|
||||||
|
kotlin.runCatching {
|
||||||
|
val kernelService = NTServiceFetcher.kernelService
|
||||||
|
val sessionService = kernelService.wrapperSession
|
||||||
|
val msgService = sessionService.msgService
|
||||||
|
val selfUin = TicketSvc.getUin()
|
||||||
|
|
||||||
|
val msgs = message.map {
|
||||||
|
if (it.asJsonObject["type"].asStringOrNull != "node") return@map ForwardMsgNode.EmptyNode // 过滤非node类型消息段
|
||||||
|
it.asJsonObject["data"].asJsonObject.let { data ->
|
||||||
|
if (data.containsKey("content")) {
|
||||||
|
data["content"].asJsonArray.forEach { msg ->
|
||||||
|
if (msg.asJsonObject["type"].asStringOrNull == "node") {
|
||||||
|
LogCenter.log("合并转发消息不支持嵌套", Level.ERROR)
|
||||||
|
return@map ForwardMsgNode.EmptyNode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ForwardMsgNode.MessageNode(
|
||||||
|
name = data["name"].asStringOrNull ?: "",
|
||||||
|
content = data["content"]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else ForwardMsgNode.MessageIdNode(data["id"].asInt)
|
||||||
|
}
|
||||||
|
}.map {
|
||||||
|
if (it is ForwardMsgNode.MessageIdNode) {
|
||||||
|
val recordResult = MsgSvc.getMsg(it.id)
|
||||||
|
if (!recordResult.isFailure) {
|
||||||
|
ForwardMsgNode.EmptyNode
|
||||||
|
} else {
|
||||||
|
val record = recordResult.getOrThrow()
|
||||||
|
ForwardMsgNode.MessageNode(
|
||||||
|
name = record.sendMemberName
|
||||||
|
.ifBlank { record.sendNickName }
|
||||||
|
.ifBlank { record.sendRemarkName }
|
||||||
|
.ifBlank { record.peerName },
|
||||||
|
content = record.toSegments().map { segment ->
|
||||||
|
segment.toJson()
|
||||||
|
}.json
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
it as ForwardMsgNode.MessageNode
|
||||||
|
}
|
||||||
|
}.filter {
|
||||||
|
it.content != null
|
||||||
|
}
|
||||||
|
|
||||||
|
val multiNodes = msgs.map { node ->
|
||||||
|
suspendCoroutine {
|
||||||
|
GlobalScope.launch {
|
||||||
|
var msgId: Long = 0
|
||||||
|
msgId = MessageHelper.sendMessageWithMsgId(MsgConstant.KCHATTYPEC2C, selfUin, node.content!!.let { msg ->
|
||||||
|
if (msg is JsonArray) msg else MessageHelper.decodeCQCode(msg.asString)
|
||||||
|
},
|
||||||
|
{ code, why ->
|
||||||
|
if (code != 0) {
|
||||||
|
error("合并转发消息节点消息发送失败:$code($why)")
|
||||||
|
}
|
||||||
|
it.resume(node.name to msgId)
|
||||||
|
}).first
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val from = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, selfUin)
|
||||||
|
val to = MessageHelper.generateContact(chatType, peerId)
|
||||||
|
|
||||||
|
val uniseq = MessageHelper.generateMsgId(chatType)
|
||||||
|
msgService.multiForwardMsg(ArrayList<MultiMsgInfo>().apply {
|
||||||
|
multiNodes.forEach { add(MultiMsgInfo(it.second, it.first)) }
|
||||||
|
}.also { it.reverse() }, from, to, MsgSvc.MessageCallback(peerId, uniseq.first))
|
||||||
|
|
||||||
|
return ok(
|
||||||
|
ForwardMessageResult(
|
||||||
|
msgId = uniseq.first,
|
||||||
|
forwardId = ""
|
||||||
|
), echo = echo)
|
||||||
|
}.onFailure {
|
||||||
|
return error("error: $it", echo)
|
||||||
|
}
|
||||||
|
return logic("合并转发消息失败(unknown error)", echo)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val requiredParams: Array<String> = arrayOf("message")
|
||||||
|
|
||||||
|
override fun path(): String = "send_forward_msg"
|
||||||
|
}
|
@ -1,233 +0,0 @@
|
|||||||
@file:OptIn(DelicateCoroutinesApi::class)
|
|
||||||
|
|
||||||
package moe.fuqiuluo.shamrock.remote.action.handlers
|
|
||||||
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MultiMsgInfo
|
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.serialization.json.JsonArray
|
|
||||||
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.qqinterface.servlet.TicketSvc
|
|
||||||
import moe.fuqiuluo.qqinterface.servlet.msg.convert.toSegments
|
|
||||||
import moe.fuqiuluo.shamrock.helper.Level
|
|
||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
|
||||||
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
|
||||||
import moe.fuqiuluo.shamrock.tools.EmptyJsonObject
|
|
||||||
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
|
|
||||||
import moe.fuqiuluo.shamrock.tools.asInt
|
|
||||||
import moe.fuqiuluo.shamrock.tools.asJsonObject
|
|
||||||
import moe.fuqiuluo.shamrock.tools.asString
|
|
||||||
import moe.fuqiuluo.shamrock.tools.asStringOrNull
|
|
||||||
import moe.fuqiuluo.shamrock.tools.json
|
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
|
||||||
import kotlin.coroutines.resume
|
|
||||||
import kotlin.coroutines.suspendCoroutine
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 合并转发消息节点数据类
|
|
||||||
*/
|
|
||||||
sealed interface ForwardMsgNode {
|
|
||||||
class MessageIdNode(
|
|
||||||
val id: Int
|
|
||||||
): ForwardMsgNode
|
|
||||||
|
|
||||||
open class MessageNode(
|
|
||||||
val name: String,
|
|
||||||
val content: JsonElement?
|
|
||||||
): ForwardMsgNode
|
|
||||||
|
|
||||||
object EmptyNode: MessageNode("", null)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 私聊合并转发
|
|
||||||
*/
|
|
||||||
internal object SendPrivateForwardMsg: IActionHandler() {
|
|
||||||
override suspend fun internalHandle(session: ActionSession): String {
|
|
||||||
val groupId = session.getString("user_id")
|
|
||||||
if (session.isArray("messages")) {
|
|
||||||
val messages = session.getArray("messages")
|
|
||||||
return invoke(messages, groupId, session.echo)
|
|
||||||
}
|
|
||||||
return logic("未知格式合并转发消息", session.echo)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend operator fun invoke(
|
|
||||||
message: JsonArray,
|
|
||||||
userId: String,
|
|
||||||
echo: JsonElement = EmptyJsonString
|
|
||||||
): String {
|
|
||||||
kotlin.runCatching {
|
|
||||||
val kernelService = NTServiceFetcher.kernelService
|
|
||||||
val sessionService = kernelService.wrapperSession
|
|
||||||
val msgService = sessionService.msgService
|
|
||||||
val selfUin = TicketSvc.getUin()
|
|
||||||
|
|
||||||
val msgs = message.map {
|
|
||||||
if (it.asJsonObject["type"].asStringOrNull != "node") return@map ForwardMsgNode.EmptyNode // 过滤非node类型消息段
|
|
||||||
it.asJsonObject["data"].asJsonObject.let { data ->
|
|
||||||
if (data.containsKey("content"))
|
|
||||||
ForwardMsgNode.MessageNode(
|
|
||||||
name = data["name"].asStringOrNull ?: "",
|
|
||||||
content = data["content"]
|
|
||||||
)
|
|
||||||
else ForwardMsgNode.MessageIdNode(data["id"].asInt)
|
|
||||||
}
|
|
||||||
}.map {
|
|
||||||
if (it is ForwardMsgNode.MessageIdNode) {
|
|
||||||
val recordResult = MsgSvc.getMsg(it.id)
|
|
||||||
if (recordResult.isFailure) {
|
|
||||||
ForwardMsgNode.EmptyNode
|
|
||||||
} else {
|
|
||||||
val record = recordResult.getOrThrow()
|
|
||||||
ForwardMsgNode.MessageNode(
|
|
||||||
name = record.sendMemberName
|
|
||||||
.ifBlank { record.sendNickName }
|
|
||||||
.ifBlank { record.sendRemarkName }
|
|
||||||
.ifBlank { record.peerName },
|
|
||||||
content = record.toSegments().map { segment ->
|
|
||||||
segment.toJson()
|
|
||||||
}.json
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
it as ForwardMsgNode.MessageNode
|
|
||||||
}
|
|
||||||
}.filter {
|
|
||||||
it.content != null
|
|
||||||
}
|
|
||||||
|
|
||||||
val multiNodes = msgs.map { node ->
|
|
||||||
suspendCoroutine {
|
|
||||||
GlobalScope.launch {
|
|
||||||
var msgId: Long = 0
|
|
||||||
msgId = MessageHelper.sendMessageWithMsgId(MsgConstant.KCHATTYPEC2C, selfUin, node.content!!.let { msg ->
|
|
||||||
if (msg is JsonArray) msg else MessageHelper.decodeCQCode(msg.asString)
|
|
||||||
}, { code, why ->
|
|
||||||
if (code != 0) {
|
|
||||||
LogCenter.log("合并转发消息节点消息发送失败:$code($why)", Level.WARN)
|
|
||||||
}
|
|
||||||
it.resume(node.name to msgId)
|
|
||||||
}).first
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val from = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, selfUin)
|
|
||||||
val to = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, userId)
|
|
||||||
msgService.multiForwardMsg(ArrayList<MultiMsgInfo>().apply {
|
|
||||||
multiNodes.forEach { add(MultiMsgInfo(it.second, it.first)) }
|
|
||||||
}.also { it.reverse() }, from, to) { code, why ->
|
|
||||||
if (code != 0)
|
|
||||||
LogCenter.log("合并转发消息:$code($why)", Level.WARN)
|
|
||||||
}
|
|
||||||
return ok(data = EmptyJsonObject, echo = echo)
|
|
||||||
}.onFailure {
|
|
||||||
return error("error: $it", echo)
|
|
||||||
}
|
|
||||||
return logic("合并转发消息失败(unknown error)", echo)
|
|
||||||
}
|
|
||||||
|
|
||||||
override val requiredParams: Array<String> = arrayOf("user_id")
|
|
||||||
|
|
||||||
override fun path(): String = "send_private_forward_msg"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 群聊合并转发
|
|
||||||
*/
|
|
||||||
internal object SendGroupForwardMsg: IActionHandler() {
|
|
||||||
override suspend fun internalHandle(session: ActionSession): String {
|
|
||||||
val groupId = session.getString("group_id")
|
|
||||||
if (session.isArray("messages")) {
|
|
||||||
val messages = session.getArray("messages")
|
|
||||||
return invoke(messages, groupId, session.echo)
|
|
||||||
}
|
|
||||||
return logic("未知格式合并转发消息", session.echo)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend operator fun invoke(
|
|
||||||
message: JsonArray,
|
|
||||||
groupId: String,
|
|
||||||
echo: JsonElement = EmptyJsonString
|
|
||||||
): String {
|
|
||||||
kotlin.runCatching {
|
|
||||||
val kernelService = NTServiceFetcher.kernelService
|
|
||||||
val sessionService = kernelService.wrapperSession
|
|
||||||
val msgService = sessionService.msgService
|
|
||||||
val selfUin = TicketSvc.getUin()
|
|
||||||
|
|
||||||
val msgs = message.map {
|
|
||||||
if (it.asJsonObject["type"].asStringOrNull != "node") return@map ForwardMsgNode.EmptyNode // 过滤非node类型消息段
|
|
||||||
it.asJsonObject["data"].asJsonObject.let { data ->
|
|
||||||
if (data.containsKey("content"))
|
|
||||||
ForwardMsgNode.MessageNode(
|
|
||||||
name = data["name"].asStringOrNull ?: "",
|
|
||||||
content = data["content"]
|
|
||||||
)
|
|
||||||
else ForwardMsgNode.MessageIdNode(data["id"].asInt)
|
|
||||||
}
|
|
||||||
}.map {
|
|
||||||
if (it is ForwardMsgNode.MessageIdNode) {
|
|
||||||
val recordResult = MsgSvc.getMsg(it.id)
|
|
||||||
if (recordResult.isFailure) {
|
|
||||||
ForwardMsgNode.EmptyNode
|
|
||||||
} else {
|
|
||||||
val record = recordResult.getOrThrow()
|
|
||||||
ForwardMsgNode.MessageNode(
|
|
||||||
name = record.sendMemberName
|
|
||||||
.ifBlank { record.sendNickName }
|
|
||||||
.ifBlank { record.sendRemarkName }
|
|
||||||
.ifBlank { record.peerName },
|
|
||||||
content = record.toSegments().map { segment ->
|
|
||||||
segment.toJson()
|
|
||||||
}.json
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
it as ForwardMsgNode.MessageNode
|
|
||||||
}
|
|
||||||
}.filter {
|
|
||||||
it.content != null
|
|
||||||
}
|
|
||||||
|
|
||||||
val multiNodes = msgs.map { node ->
|
|
||||||
suspendCoroutine {
|
|
||||||
GlobalScope.launch {
|
|
||||||
var msgId: Long = 0
|
|
||||||
msgId = MessageHelper.sendMessageWithMsgId(MsgConstant.KCHATTYPEC2C, selfUin, node.content!!.let { msg ->
|
|
||||||
if (msg is JsonArray) msg else MessageHelper.decodeCQCode(msg.asString)
|
|
||||||
}, { code, why ->
|
|
||||||
if (code != 0) {
|
|
||||||
LogCenter.log("合并转发消息节点消息发送失败:$code($why)", Level.WARN)
|
|
||||||
}
|
|
||||||
it.resume(node.name to msgId)
|
|
||||||
}).first
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val from = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, selfUin)
|
|
||||||
val to = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, groupId)
|
|
||||||
msgService.multiForwardMsg(ArrayList<MultiMsgInfo>().apply {
|
|
||||||
multiNodes.forEach { add(MultiMsgInfo(it.second, it.first)) }
|
|
||||||
}.also { it.reverse() }, from, to) { code, why ->
|
|
||||||
if (code != 0)
|
|
||||||
LogCenter.log("合并转发消息:$code($why)", Level.WARN)
|
|
||||||
}
|
|
||||||
return ok(data = EmptyJsonObject, echo = echo)
|
|
||||||
}.onFailure {
|
|
||||||
return error("error: $it", echo)
|
|
||||||
}
|
|
||||||
return logic("合并转发消息失败(unknown error)", echo)
|
|
||||||
}
|
|
||||||
|
|
||||||
override val requiredParams: Array<String> = arrayOf("group_id")
|
|
||||||
|
|
||||||
override fun path(): String = "send_group_forward_msg"
|
|
||||||
}
|
|
@ -0,0 +1,21 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
internal object SendGroupForwardMessage: IActionHandler() {
|
||||||
|
override suspend fun internalHandle(session: ActionSession): String {
|
||||||
|
val groupId = session.getString("group_id")
|
||||||
|
return if (session.isArray("messages")) {
|
||||||
|
val messages = session.getArray("messages")
|
||||||
|
SendForwardMessage(MsgConstant.KCHATTYPEGROUP, groupId, messages, session.echo)
|
||||||
|
} else {
|
||||||
|
logic("未知格式合并转发消息", session.echo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val requiredParams: Array<String> = arrayOf("messages", "group_id")
|
||||||
|
|
||||||
|
override fun path(): String = "send_group_forward_msg"
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
internal object SendPrivateForwardMessage : IActionHandler() {
|
||||||
|
override suspend fun internalHandle(session: ActionSession): String {
|
||||||
|
val userId = session.getString("user_id")
|
||||||
|
return if (session.isArray("messages")) {
|
||||||
|
val messages = session.getArray("messages")
|
||||||
|
SendForwardMessage(MsgConstant.KCHATTYPEC2C, userId, messages, session.echo)
|
||||||
|
} else {
|
||||||
|
logic("未知格式合并转发消息", session.echo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val requiredParams: Array<String> = arrayOf("messages", "user_id")
|
||||||
|
|
||||||
|
override fun path(): String = "send_private_forward_msg"
|
||||||
|
}
|
@ -9,12 +9,12 @@ internal object SendPrivateMessage: IActionHandler() {
|
|||||||
override suspend fun internalHandle(session: ActionSession): String {
|
override suspend fun internalHandle(session: ActionSession): String {
|
||||||
val userId = session.getString("user_id")
|
val userId = session.getString("user_id")
|
||||||
val groupId = session.getStringOrNull("group_id")
|
val groupId = session.getStringOrNull("group_id")
|
||||||
val chatTYpe = if (groupId == null) MsgConstant.KCHATTYPEC2C else MsgConstant.KCHATTYPETEMPC2CFROMGROUP
|
val chatType = if (groupId == null) MsgConstant.KCHATTYPEC2C else MsgConstant.KCHATTYPETEMPC2CFROMGROUP
|
||||||
return if (session.isString("message")) {
|
return if (session.isString("message")) {
|
||||||
val autoEscape = session.getBooleanOrDefault("auto_escape", false)
|
val autoEscape = session.getBooleanOrDefault("auto_escape", false)
|
||||||
val message = session.getString("message")
|
val message = session.getString("message")
|
||||||
SendMessage.invoke(
|
SendMessage.invoke(
|
||||||
chatType = chatTYpe,
|
chatType = chatType,
|
||||||
peerId = userId,
|
peerId = userId,
|
||||||
message = message,
|
message = message,
|
||||||
autoEscape = autoEscape,
|
autoEscape = autoEscape,
|
||||||
@ -24,7 +24,7 @@ internal object SendPrivateMessage: IActionHandler() {
|
|||||||
} else if (session.isArray("message")) {
|
} else if (session.isArray("message")) {
|
||||||
val message = session.getArray("message")
|
val message = session.getArray("message")
|
||||||
SendMessage(
|
SendMessage(
|
||||||
chatType = chatTYpe,
|
chatType = chatType,
|
||||||
peerId = userId,
|
peerId = userId,
|
||||||
message = message,
|
message = message,
|
||||||
echo = session.echo,
|
echo = session.echo,
|
||||||
@ -33,7 +33,7 @@ internal object SendPrivateMessage: IActionHandler() {
|
|||||||
} else {
|
} else {
|
||||||
val message = session.getObject("message")
|
val message = session.getObject("message")
|
||||||
SendMessage(
|
SendMessage(
|
||||||
chatType = chatTYpe,
|
chatType = chatType,
|
||||||
peerId = userId,
|
peerId = userId,
|
||||||
message = listOf( message ).jsonArray,
|
message = listOf( message ).jsonArray,
|
||||||
echo = session.echo,
|
echo = session.echo,
|
||||||
|
@ -33,20 +33,22 @@ fun Routing.messageAction() {
|
|||||||
post {
|
post {
|
||||||
val groupId = fetchPostOrNull("group_id")
|
val groupId = fetchPostOrNull("group_id")
|
||||||
val messages = fetchPostJsonArray("messages")
|
val messages = fetchPostJsonArray("messages")
|
||||||
call.respondText(SendGroupForwardMsg(messages, groupId ?: ""), ContentType.Application.Json)
|
call.respondText(SendForwardMessage(MsgConstant.KCHATTYPEGROUP, groupId ?: "", messages), ContentType.Application.Json)
|
||||||
}
|
}
|
||||||
get {
|
get {
|
||||||
respond(false, Status.InternalHandlerError, "Not support GET method")
|
respond(false, Status.InternalHandlerError, "Not support GET method")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
post("/send_group_forward_msg") {
|
|
||||||
|
|
||||||
}
|
route("/send_private_forward_msg") {
|
||||||
|
post {
|
||||||
post("/send_private_forward_msg") {
|
val userId = fetchPostOrNull("user_id")
|
||||||
val userId = fetchPostOrNull("user_id")
|
val messages = fetchPostJsonArray("messages")
|
||||||
val messages = fetchPostJsonArray("messages")
|
call.respondText(SendForwardMessage(MsgConstant.KCHATTYPEC2C, userId ?: "", messages), ContentType.Application.Json)
|
||||||
call.respondText(SendPrivateForwardMsg(messages, userId ?: ""), ContentType.Application.Json)
|
}
|
||||||
|
get {
|
||||||
|
respond(false, Status.InternalHandlerError, "Not support GET method")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getOrPost("/get_forward_msg") {
|
getOrPost("/get_forward_msg") {
|
||||||
|
@ -7,7 +7,6 @@ import kotlinx.coroutines.GlobalScope
|
|||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import moe.fuqiuluo.shamrock.remote.service.api.WebSocketClientServlet
|
import moe.fuqiuluo.shamrock.remote.service.api.WebSocketClientServlet
|
||||||
import moe.fuqiuluo.shamrock.remote.service.data.push.*
|
|
||||||
import moe.fuqiuluo.shamrock.helper.Level
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
import moe.fuqiuluo.shamrock.remote.service.api.GlobalEventTransmitter
|
import moe.fuqiuluo.shamrock.remote.service.api.GlobalEventTransmitter
|
||||||
@ -35,7 +34,7 @@ internal class WebSocketClientService(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
submitFlowJob(GlobalScope.launch {
|
submitFlowJob(GlobalScope.launch {
|
||||||
GlobalEventTransmitter.onRequestEvent() { event ->
|
GlobalEventTransmitter.onRequestEvent { event ->
|
||||||
pushTo(event)
|
pushTo(event)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -33,16 +33,17 @@ internal object ShamrockConfig {
|
|||||||
val mmkv = MMKVFetcher.mmkvWithId("shamrock_config")
|
val mmkv = MMKVFetcher.mmkvWithId("shamrock_config")
|
||||||
mmkv.apply {
|
mmkv.apply {
|
||||||
putBoolean( "tablet", intent.getBooleanExtra("tablet", false)) // 强制平板模式
|
putBoolean( "tablet", intent.getBooleanExtra("tablet", false)) // 强制平板模式
|
||||||
putInt( "port", intent.getIntExtra("port", 5700)) // 主动HTTP端口
|
putInt( "port", intent.getIntExtra("port", 5700)) // 主动HTTP端口
|
||||||
putBoolean( "ws", intent.getBooleanExtra("ws", false)) // 主动WS开关
|
putBoolean( "ws", intent.getBooleanExtra("ws", false)) // 主动WS开关
|
||||||
putBoolean( "http", intent.getBooleanExtra("http", false)) // HTTP回调开关
|
putBoolean( "http", intent.getBooleanExtra("http", false)) // HTTP回调开关
|
||||||
putString( "http_addr", intent.getStringExtra("http_addr")) // WebHook回调地址
|
putString( "http_addr", intent.getStringExtra("http_addr")) // WebHook回调地址
|
||||||
putBoolean( "ws_client", intent.getBooleanExtra("ws_client", false)) // 被动WS开关
|
putBoolean( "ws_client", intent.getBooleanExtra("ws_client", false)) // 被动WS开关
|
||||||
putBoolean( "use_cqcode", intent.getBooleanExtra("use_cqcode", false)) // 使用CQ码
|
putBoolean( "use_cqcode", intent.getBooleanExtra("use_cqcode", false)) // 使用CQ码
|
||||||
putBoolean( "inject_packet", intent.getBooleanExtra("inject_packet", false)) // 拦截无用包
|
putBoolean( "inject_packet", intent.getBooleanExtra("inject_packet", false)) // 拦截无用包
|
||||||
putBoolean( "debug", intent.getBooleanExtra("debug", false)) // 调试模式
|
putBoolean( "debug", intent.getBooleanExtra("debug", false)) // 调试模式
|
||||||
|
|
||||||
Config.defaultToken = intent.getStringExtra("token")
|
Config.defaultToken = intent.getStringExtra("token")
|
||||||
|
Config.antiTrace = intent.getBooleanExtra("anti_qq_trace", true)
|
||||||
|
|
||||||
val wsPort = intent.getIntExtra("ws_port", 5800)
|
val wsPort = intent.getIntExtra("ws_port", 5800)
|
||||||
Config.activeWebSocket = if (Config.activeWebSocket == null) ConnectionConfig(
|
Config.activeWebSocket = if (Config.activeWebSocket == null) ConnectionConfig(
|
||||||
@ -58,16 +59,16 @@ internal object ShamrockConfig {
|
|||||||
ConnectionConfig(address = it)
|
ConnectionConfig(address = it)
|
||||||
}?.toMutableList()
|
}?.toMutableList()
|
||||||
|
|
||||||
putString( "key_store", intent.getStringExtra("key_store")) // 证书路径
|
putString( "key_store", intent.getStringExtra("key_store")) // 证书路径
|
||||||
putString( "ssl_pwd", intent.getStringExtra("ssl_pwd")) // 证书密码
|
putString( "ssl_pwd", intent.getStringExtra("ssl_pwd")) // 证书密码
|
||||||
putString( "ssl_private_pwd", intent.getStringExtra("ssl_private_pwd")) // 证书私钥密码
|
putString( "ssl_private_pwd", intent.getStringExtra("ssl_private_pwd")) // 证书私钥密码
|
||||||
putString( "ssl_alias", intent.getStringExtra("ssl_alias")) // 证书别名
|
putString( "ssl_alias", intent.getStringExtra("ssl_alias")) // 证书别名
|
||||||
putInt( "ssl_port", intent.getIntExtra("ssl_port", 5701)) // 主动HTTP端口
|
putInt( "ssl_port", intent.getIntExtra("ssl_port", 5701)) // 主动HTTP端口
|
||||||
|
|
||||||
putBoolean("auto_clear", intent.getBooleanExtra("auto_clear", false)) // 自动清理
|
putBoolean("auto_clear", intent.getBooleanExtra("auto_clear", false)) // 自动清理
|
||||||
|
|
||||||
putBoolean("enable_self_msg", intent.getBooleanExtra("enable_self_msg", false)) // 推送自己发的消息
|
putBoolean("enable_self_msg", intent.getBooleanExtra("enable_self_msg", false)) // 推送自己发的消息
|
||||||
putBoolean("shell", intent.getBooleanExtra("shell", false)) // 开启Shell接口
|
putBoolean("shell", intent.getBooleanExtra("shell", false)) // 开启Shell接口
|
||||||
|
|
||||||
putBoolean("isInit", true)
|
putBoolean("isInit", true)
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,11 @@ internal data class MessageResult(
|
|||||||
@SerialName("message_id") val msgId: Int,
|
@SerialName("message_id") val msgId: Int,
|
||||||
@SerialName("time") val time: Long
|
@SerialName("time") val time: Long
|
||||||
)
|
)
|
||||||
|
@Serializable
|
||||||
|
internal data class ForwardMessageResult(
|
||||||
|
@SerialName("message_id") val msgId: Int,
|
||||||
|
@SerialName("forward_id") val forwardId: String
|
||||||
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal data class MessageDetail(
|
internal data class MessageDetail(
|
||||||
|
@ -5,6 +5,8 @@ import de.robv.android.xposed.IXposedHookLoadPackage
|
|||||||
import de.robv.android.xposed.XposedBridge
|
import de.robv.android.xposed.XposedBridge
|
||||||
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
||||||
import de.robv.android.xposed.XposedBridge.log
|
import de.robv.android.xposed.XposedBridge.log
|
||||||
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
import moe.fuqiuluo.shamrock.utils.MMKVFetcher
|
import moe.fuqiuluo.shamrock.utils.MMKVFetcher
|
||||||
import moe.fuqiuluo.shamrock.xposed.loader.ActionLoader
|
import moe.fuqiuluo.shamrock.xposed.loader.ActionLoader
|
||||||
import moe.fuqiuluo.shamrock.xposed.loader.FuckAMS
|
import moe.fuqiuluo.shamrock.xposed.loader.FuckAMS
|
||||||
@ -12,6 +14,7 @@ import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader
|
|||||||
import moe.fuqiuluo.shamrock.tools.FuzzySearchClass
|
import moe.fuqiuluo.shamrock.tools.FuzzySearchClass
|
||||||
import moe.fuqiuluo.shamrock.tools.afterHook
|
import moe.fuqiuluo.shamrock.tools.afterHook
|
||||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.loader.NativeLoader
|
||||||
import mqq.app.MobileQQ
|
import mqq.app.MobileQQ
|
||||||
import java.lang.reflect.Field
|
import java.lang.reflect.Field
|
||||||
import java.lang.reflect.Modifier
|
import java.lang.reflect.Modifier
|
||||||
@ -26,6 +29,12 @@ internal class XposedEntry: IXposedHookLoadPackage {
|
|||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
var sec_static_stage_inited = false
|
var sec_static_stage_inited = false
|
||||||
|
@JvmStatic
|
||||||
|
var sec_static_nativehook_inited = false
|
||||||
|
|
||||||
|
external fun injected(): Boolean
|
||||||
|
|
||||||
|
external fun hasEnv(): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
private var firstStageInit = false
|
private var firstStageInit = false
|
||||||
@ -108,7 +117,6 @@ internal class XposedEntry: IXposedHookLoadPackage {
|
|||||||
if (sec_static_stage_inited) return
|
if (sec_static_stage_inited) return
|
||||||
|
|
||||||
val classLoader = ctx.classLoader.also { requireNotNull(it) }
|
val classLoader = ctx.classLoader.also { requireNotNull(it) }
|
||||||
|
|
||||||
LuoClassloader.hostClassLoader = classLoader
|
LuoClassloader.hostClassLoader = classLoader
|
||||||
|
|
||||||
if(injectClassloader(XposedEntry::class.java.classLoader)) {
|
if(injectClassloader(XposedEntry::class.java.classLoader)) {
|
||||||
@ -116,12 +124,7 @@ internal class XposedEntry: IXposedHookLoadPackage {
|
|||||||
System.setProperty("qxbot_flag", "1")
|
System.setProperty("qxbot_flag", "1")
|
||||||
} else return
|
} else return
|
||||||
|
|
||||||
log("Process Name = " + MobileQQ.getMobileQQ().qqProcessName.apply {
|
log("Process Name = " + MobileQQ.getMobileQQ().qqProcessName)
|
||||||
// if (!contains("msf", ignoreCase = true)) return // 非MSF进程 退出
|
|
||||||
//if (contains("peak")) {
|
|
||||||
// PlatformUtils.killProcess(ctx, this)
|
|
||||||
//}
|
|
||||||
})
|
|
||||||
|
|
||||||
PlatformUtils.isTim()
|
PlatformUtils.isTim()
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import android.content.Context
|
|||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.pm.VersionedPackage
|
import android.content.pm.VersionedPackage
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import de.robv.android.xposed.XC_MethodHook
|
|
||||||
import de.robv.android.xposed.XC_MethodReplacement
|
import de.robv.android.xposed.XC_MethodReplacement
|
||||||
import de.robv.android.xposed.XposedBridge
|
import de.robv.android.xposed.XposedBridge
|
||||||
import de.robv.android.xposed.XposedHelpers
|
import de.robv.android.xposed.XposedHelpers
|
||||||
@ -13,8 +12,9 @@ import moe.fuqiuluo.shamrock.helper.Level
|
|||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
||||||
import moe.fuqiuluo.shamrock.tools.hookMethod
|
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.LuoClassloader
|
||||||
import mqq.app.MobileQQ
|
import moe.fuqiuluo.shamrock.xposed.loader.NativeLoader
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 反检测
|
* 反检测
|
||||||
@ -22,6 +22,7 @@ import mqq.app.MobileQQ
|
|||||||
class AntiDetection: IAction {
|
class AntiDetection: IAction {
|
||||||
override fun invoke(ctx: Context) {
|
override fun invoke(ctx: Context) {
|
||||||
antiFindPackage(ctx)
|
antiFindPackage(ctx)
|
||||||
|
antiNativeDetection()
|
||||||
if (ShamrockConfig.isAntiTrace())
|
if (ShamrockConfig.isAntiTrace())
|
||||||
antiTrace()
|
antiTrace()
|
||||||
antiMemoryWalking()
|
antiMemoryWalking()
|
||||||
@ -38,6 +39,23 @@ class AntiDetection: IAction {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun antiNativeDetection() {
|
||||||
|
try {
|
||||||
|
//System.loadLibrary("clover")
|
||||||
|
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.sec_static_nativehook_inited = true
|
||||||
|
LogCenter.log("[Shamrock] Shamrock反检测启动成功", Level.INFO)
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
LogCenter.log("[Shamrock] Shamrock反检测启动失败,请检查LSPosed版本使用大于100: ${e.message}", Level.ERROR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun antiFindPackage(context: Context) {
|
private fun antiFindPackage(context: Context) {
|
||||||
val packageManager = context.packageManager
|
val packageManager = context.packageManager
|
||||||
val applicationInfo = packageManager.getApplicationInfo("moe.fuqiuluo.shamrock", 0)
|
val applicationInfo = packageManager.getApplicationInfo("moe.fuqiuluo.shamrock", 0)
|
||||||
|
@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
|||||||
import de.robv.android.xposed.XposedBridge
|
import de.robv.android.xposed.XposedBridge
|
||||||
import moe.fuqiuluo.shamrock.helper.Level
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.XposedEntry
|
||||||
import mqq.app.MobileQQ
|
import mqq.app.MobileQQ
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@ -16,15 +17,13 @@ internal object NativeLoader {
|
|||||||
return externalLibPath.resolve("libffmpegkit.so").exists()
|
return externalLibPath.resolve("libffmpegkit.so").exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var isInitShamrock = false
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 使目标进程可以使用来自模块的库
|
* 使目标进程可以使用来自模块的库
|
||||||
*/
|
*/
|
||||||
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
||||||
fun load(name: String) {
|
fun load(name: String) {
|
||||||
try {
|
try {
|
||||||
if (name == "shamrock") {
|
if (name == "shamrock" || name == "clover") {
|
||||||
val context = MobileQQ.getContext()
|
val context = MobileQQ.getContext()
|
||||||
val packageManager = context.packageManager
|
val packageManager = context.packageManager
|
||||||
val applicationInfo = packageManager.getApplicationInfo("moe.fuqiuluo.shamrock.hided", 0)
|
val applicationInfo = packageManager.getApplicationInfo("moe.fuqiuluo.shamrock.hided", 0)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user