mirror of
https://github.com/whitechi73/OpenShamrock.git
synced 2024-08-14 05:12:17 +00:00
Compare commits
7 Commits
12d594697d
...
661680e60b
Author | SHA1 | Date | |
---|---|---|---|
661680e60b | |||
54b7eb95a8 | |||
265fff3cd2 | |||
8ca0a3815a | |||
da6d34c53e | |||
61ffb37951 | |||
593f461ffe |
@ -162,6 +162,17 @@ object ShamrockConfig {
|
|||||||
pushUpdate(ctx)
|
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 {
|
fun getHttpPort(ctx: Context): Int {
|
||||||
val preferences = ctx.getSharedPreferences("config", 0)
|
val preferences = ctx.getSharedPreferences("config", 0)
|
||||||
return preferences.getInt("port", 5700)
|
return preferences.getInt("port", 5700)
|
||||||
@ -354,6 +365,7 @@ object ShamrockConfig {
|
|||||||
"disable_auto_sync_setting" to preferences.getBoolean("disable_auto_sync_setting", false),
|
"disable_auto_sync_setting" to preferences.getBoolean("disable_auto_sync_setting", false),
|
||||||
"forbid_useless_process" to preferences.getBoolean("forbid_useless_process", false),
|
"forbid_useless_process" to preferences.getBoolean("forbid_useless_process", false),
|
||||||
"enable_old_bdh" to preferences.getBoolean("enable_old_bdh", false),
|
"enable_old_bdh" to preferences.getBoolean("enable_old_bdh", false),
|
||||||
|
"up_res_group" to preferences.getString("up_res_group", ""),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,6 +356,36 @@ private fun FunctionCard(
|
|||||||
return@Function true
|
return@Function true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
val uploadResourceGroup = remember { mutableStateOf(ShamrockConfig.getUploadResourceGroup(ctx)) }
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.absolutePadding(left = 8.dp, right = 8.dp, top = 12.dp, bottom = 0.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(2.dp),
|
||||||
|
text = "用来上传资源的群聊,错误的资源上传终点可能导致封禁,请自建一个群聊并填写在下方。",
|
||||||
|
color = Color.Red,
|
||||||
|
fontSize = 11.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
TextItem(
|
||||||
|
title = "接受资源群聊",
|
||||||
|
desc = "用来上传资源的群聊,请自建一个群聊并填写在下方。",
|
||||||
|
text = uploadResourceGroup,
|
||||||
|
hint = "请输入群号",
|
||||||
|
error = "群号不合法",
|
||||||
|
checker = {
|
||||||
|
it.isNotBlank() && it.toULongOrNull() != null
|
||||||
|
},
|
||||||
|
confirm = {
|
||||||
|
val groupId = uploadResourceGroup.value
|
||||||
|
ShamrockConfig.setUploadResourceGroup(ctx, groupId)
|
||||||
|
AppRuntime.log("设置接受资源群聊为[$groupId]。")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Function(
|
Function(
|
||||||
title = "专业级接口",
|
title = "专业级接口",
|
||||||
@ -445,9 +475,7 @@ private fun InfoItem(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.combinedClickable(onDoubleClick = {
|
.combinedClickable(onDoubleClick = {
|
||||||
doubleClick?.invoke(content)
|
doubleClick?.invoke(content)
|
||||||
}) {
|
}) { true }
|
||||||
true
|
|
||||||
}
|
|
||||||
,
|
,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
|
@ -32,6 +32,11 @@ data class AdaptShareInfoReq(
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Template(
|
data class Template(
|
||||||
@ProtoNumber(1) var templateId: UInt? = null,
|
@ProtoNumber(1) var templateId: ULong? = null,
|
||||||
@ProtoNumber(2) var templateData: ByteArray? = null,
|
@ProtoNumber(2) var templateData: ByteArray? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AdaptShareInfoResp(
|
||||||
|
@ProtoNumber(2) var json: String? = null,
|
||||||
|
): Protobuf<AdaptShareInfoResp>
|
@ -7,6 +7,8 @@ import kotlinx.serialization.Serializable
|
|||||||
import kotlinx.serialization.protobuf.ProtoNumber
|
import kotlinx.serialization.protobuf.ProtoNumber
|
||||||
import moe.fuqiuluo.symbols.Protobuf
|
import moe.fuqiuluo.symbols.Protobuf
|
||||||
|
|
||||||
|
const val DEFAULT_DEVICE_INFO = "i=&imsi=&mac=02:00:00:00:00:00&m=Shamrock&o=114514&a=1919810&sd=0&c64=1&sc=1&p=8000*8000&aid=123456789012345678901234567890abcdef&f=Tencent&mm=5610&cf=1726&cc=8&qimei=&qimei36=&sharpP=1&n=nether_world&support_xsj_live=false&client_mod=concise&timezone=America/La_Paz&material_sdk_version=&vh265=&refreshrate=10086&hwlevel=9&suphdr=1&is_teenager_mod=8&liveH265=&bmst=5&AV1=0"
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class QWebReq(
|
data class QWebReq(
|
||||||
@ProtoNumber(1) val seq: Int = 0,
|
@ProtoNumber(1) val seq: Int = 0,
|
||||||
|
@ -37,6 +37,7 @@ import protobuf.oidb.cmx0xf57.Oidb0xf57Req
|
|||||||
import protobuf.oidb.cmx0xf57.Oidb0xf57Rsp
|
import protobuf.oidb.cmx0xf57.Oidb0xf57Rsp
|
||||||
import protobuf.oidb.cmx0xf57.Oidb0xf57U1
|
import protobuf.oidb.cmx0xf57.Oidb0xf57U1
|
||||||
import protobuf.oidb.cmx0xf57.Oidb0xf57U2
|
import protobuf.oidb.cmx0xf57.Oidb0xf57U2
|
||||||
|
import protobuf.qweb.DEFAULT_DEVICE_INFO
|
||||||
import protobuf.qweb.QWebExtInfo
|
import protobuf.qweb.QWebExtInfo
|
||||||
import protobuf.qweb.QWebReq
|
import protobuf.qweb.QWebReq
|
||||||
import protobuf.qweb.QWebRsp
|
import protobuf.qweb.QWebRsp
|
||||||
@ -73,7 +74,7 @@ internal object GProSvc: BaseSvc() {
|
|||||||
val buffer = sendBufferAW("QChannelSvr.trpc.qchannel.commreader.ComReader.GetGuildFeeds", true, QWebReq(
|
val buffer = sendBufferAW("QChannelSvr.trpc.qchannel.commreader.ComReader.GetGuildFeeds", true, QWebReq(
|
||||||
seq = 10,
|
seq = 10,
|
||||||
qua = PlatformUtils.getQUA(),
|
qua = PlatformUtils.getQUA(),
|
||||||
deviceInfo = "i=&imsi=&mac=02:00:00:00:00:00&m=Shamrock&o=114514&a=1919810&sd=0&c64=1&sc=1&p=8000*8000&aid=123456789012345678901234567890abcdef&f=Tencent&mm=5610&cf=1726&cc=8&qimei=&qimei36=&sharpP=1&n=nether_world&support_xsj_live=false&client_mod=concise&timezone=America/La_Paz&material_sdk_version=&vh265=&refreshrate=10086&hwlevel=9&suphdr=1&is_teenager_mod=8&liveH265=&bmst=5&AV1=0",
|
deviceInfo = DEFAULT_DEVICE_INFO,
|
||||||
buffer = GetGuildFeedsReq(
|
buffer = GetGuildFeedsReq(
|
||||||
count = 12,
|
count = 12,
|
||||||
from = startIndex,
|
from = startIndex,
|
||||||
|
@ -338,13 +338,13 @@ internal object MsgSvc : BaseSvc() {
|
|||||||
else -> MessageHelper.decodeCQCode(data["content"].asString)
|
else -> MessageHelper.decodeCQCode(data["content"].asString)
|
||||||
}.onEach { element ->
|
}.onEach { element ->
|
||||||
val elementData = element.asJsonObject["data"].asJsonObject
|
val elementData = element.asJsonObject["data"].asJsonObject
|
||||||
if (element.asJsonObject["type"].asString == "forward")
|
if (element.asJsonObject["type"].asString == "forward") {
|
||||||
forwardMsg[elementData["filename"].asString] =
|
forwardMsg[elementData["filename"].asString] =
|
||||||
elementData["id"].asString
|
elementData["id"].asString
|
||||||
|
}
|
||||||
}
|
}
|
||||||
).getOrElse { throw Exception("消息合成失败: $it") }.let {
|
).getOrElse { error("消息合成失败: $it") }.let {
|
||||||
desc[++i] =
|
desc[++i] = (data["name"].asStringOrNull ?: data["uin"].asStringOrNull
|
||||||
(data["name"].asStringOrNull ?: data["uin"].asStringOrNull
|
|
||||||
?: TicketSvc.getNickname()) + ": " + it.first
|
?: TicketSvc.getNickname()) + ": " + it.first
|
||||||
it.second
|
it.second
|
||||||
}
|
}
|
||||||
@ -353,8 +353,9 @@ internal object MsgSvc : BaseSvc() {
|
|||||||
} else {
|
} else {
|
||||||
error("消息节点缺少id或content字段")
|
error("消息节点缺少id或content字段")
|
||||||
}
|
}
|
||||||
|
}.onFailure {
|
||||||
|
LogCenter.log("消息节点解析失败:${it.stackTraceToString()}", Level.WARN)
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
LogCenter.log("消息节点解析失败:$it", Level.WARN)
|
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}.ifEmpty { return Result.failure(Exception("消息节点为空")) }
|
}.ifEmpty { return Result.failure(Exception("消息节点为空")) }
|
||||||
|
@ -1,9 +1,45 @@
|
|||||||
package moe.fuqiuluo.qqinterface.servlet.ark
|
package moe.fuqiuluo.qqinterface.servlet.ark
|
||||||
|
|
||||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
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() {
|
internal object LightAppSvc: BaseSvc() {
|
||||||
suspend fun adaptShare() {
|
suspend fun adaptShareJumpUrl(
|
||||||
|
arkAppInfo: ArkAppInfo,
|
||||||
|
coverUrl: String,
|
||||||
|
desc: String,
|
||||||
|
url: String
|
||||||
|
): Result<String> {
|
||||||
|
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<QWebRsp>()?.buffer?.decodeProtobuf<AdaptShareInfoResp>()
|
||||||
|
if (rsp == null || rsp.json.isNullOrEmpty())
|
||||||
|
return Result.failure(Exception("unable to adapt ShareInfo"))
|
||||||
|
return Result.success(rsp.json!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,7 +5,8 @@ sealed class ArkAppInfo(
|
|||||||
val version: String,
|
val version: String,
|
||||||
val packageName: String,
|
val packageName: String,
|
||||||
val signature: String,
|
val signature: String,
|
||||||
val miniAppId: Long = 0
|
val miniAppId: Long = 0,
|
||||||
|
val appName: String = ""
|
||||||
) {
|
) {
|
||||||
data object QQMusic: ArkAppInfo(
|
data object QQMusic: ArkAppInfo(
|
||||||
appId = 100497308,
|
appId = 100497308,
|
||||||
@ -25,7 +26,8 @@ sealed class ArkAppInfo(
|
|||||||
version = "0.0.0",
|
version = "0.0.0",
|
||||||
packageName = "tv.danmaku.bili",
|
packageName = "tv.danmaku.bili",
|
||||||
signature = "7194d531cbe7960a22007b9f6bdaa38b",
|
signature = "7194d531cbe7960a22007b9f6bdaa38b",
|
||||||
miniAppId = 1109937557
|
miniAppId = 1109937557,
|
||||||
|
appName = "哔哩哔哩"
|
||||||
)
|
)
|
||||||
|
|
||||||
data object Docs: ArkAppInfo(
|
data object Docs: ArkAppInfo(
|
||||||
|
@ -261,7 +261,7 @@ internal class ElemMaker {
|
|||||||
resources = arrayListOf(file),
|
resources = arrayListOf(file),
|
||||||
timeout = 30.seconds
|
timeout = 30.seconds
|
||||||
).getOrThrow().first()
|
).getOrThrow().first()
|
||||||
LogCenter.log(uploadRet.toString(), Level.DEBUG)
|
LogCenter.log({ uploadRet.toString() }, Level.DEBUG)
|
||||||
|
|
||||||
val elem = when (chatType) {
|
val elem = when (chatType) {
|
||||||
MsgConstant.KCHATTYPEGROUP -> Elem(
|
MsgConstant.KCHATTYPEGROUP -> Elem(
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
package moe.fuqiuluo.qqinterface.servlet.transfile
|
package moe.fuqiuluo.qqinterface.servlet.transfile
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.media.MediaMetadataRetriever
|
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterface
|
||||||
import com.tencent.mobileqq.qroute.QRoute
|
import com.tencent.mobileqq.qroute.QRoute
|
||||||
import com.tencent.qqnt.aio.adapter.api.IAIOPttApi
|
import com.tencent.qqnt.aio.adapter.api.IAIOPttApi
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.CommonFileInfo
|
import com.tencent.qqnt.kernel.nativeinterface.CommonFileInfo
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.Contact
|
import com.tencent.qqnt.kernel.nativeinterface.Contact
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.FileElement
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.PicElement
|
import com.tencent.qqnt.kernel.nativeinterface.PicElement
|
||||||
@ -16,28 +13,19 @@ import com.tencent.qqnt.kernel.nativeinterface.PttElement
|
|||||||
import com.tencent.qqnt.kernel.nativeinterface.QQNTWrapperUtil
|
import com.tencent.qqnt.kernel.nativeinterface.QQNTWrapperUtil
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.RichMediaFilePathInfo
|
import com.tencent.qqnt.kernel.nativeinterface.RichMediaFilePathInfo
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.VideoElement
|
import com.tencent.qqnt.kernel.nativeinterface.VideoElement
|
||||||
import com.tencent.qqnt.msg.api.IMsgUtilApi
|
|
||||||
import kotlinx.atomicfu.atomic
|
import kotlinx.atomicfu.atomic
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.transfile.data.Private
|
|
||||||
import moe.fuqiuluo.qqinterface.servlet.transfile.data.Troop
|
|
||||||
import moe.fuqiuluo.qqinterface.servlet.transfile.data.TryUpPicData
|
import moe.fuqiuluo.qqinterface.servlet.transfile.data.TryUpPicData
|
||||||
import moe.fuqiuluo.qqinterface.servlet.transfile.data.VideoResource
|
|
||||||
import moe.fuqiuluo.shamrock.helper.Level
|
|
||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
||||||
import moe.fuqiuluo.shamrock.helper.TransfileHelper
|
|
||||||
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
||||||
import moe.fuqiuluo.shamrock.tools.hex2ByteArray
|
import moe.fuqiuluo.shamrock.tools.hex2ByteArray
|
||||||
import moe.fuqiuluo.shamrock.tools.slice
|
import moe.fuqiuluo.shamrock.tools.slice
|
||||||
import moe.fuqiuluo.shamrock.utils.AudioUtils
|
import moe.fuqiuluo.shamrock.utils.AudioUtils
|
||||||
import moe.fuqiuluo.shamrock.utils.FileUtils
|
import moe.fuqiuluo.shamrock.utils.FileUtils
|
||||||
import moe.fuqiuluo.shamrock.utils.MD5
|
|
||||||
import moe.fuqiuluo.shamrock.utils.MediaType
|
import moe.fuqiuluo.shamrock.utils.MediaType
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.msgService
|
import moe.fuqiuluo.shamrock.xposed.helper.msgService
|
||||||
@ -63,7 +51,6 @@ import protobuf.oidb.cmd0x388.Cmd0x388ReqBody
|
|||||||
import protobuf.oidb.cmd0x388.Cmd0x388RspBody
|
import protobuf.oidb.cmd0x388.Cmd0x388RspBody
|
||||||
import protobuf.oidb.cmd0x388.TryUpImgReq
|
import protobuf.oidb.cmd0x388.TryUpImgReq
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
@ -72,14 +59,30 @@ import kotlin.random.nextULong
|
|||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
|
||||||
internal object NtV2RichMediaSvc: BaseSvc() {
|
internal object NtV2RichMediaSvc: BaseSvc() {
|
||||||
private const val GROUP_PIC_UPLOAD_TO = "100000000"
|
|
||||||
|
|
||||||
private val requestIdSeq = atomic(2L)
|
private val requestIdSeq = atomic(2L)
|
||||||
|
|
||||||
|
private fun fetchGroupResUploadTo(): String {
|
||||||
|
return ShamrockConfig.getUpResGroup().ifEmpty { "100000000" }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun tryUploadResourceByNt(
|
||||||
|
chatType: Int,
|
||||||
|
elementType: Int,
|
||||||
|
resources: ArrayList<File>,
|
||||||
|
timeout: Duration,
|
||||||
|
retryCnt: Int = 5
|
||||||
|
): Result<MutableList<CommonFileInfo>> {
|
||||||
|
return internalTryUploadResourceByNt(chatType, elementType, resources, timeout).onFailure {
|
||||||
|
if (retryCnt > 0) {
|
||||||
|
return tryUploadResourceByNt(chatType, elementType, resources, timeout, retryCnt - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量上传图片
|
* 批量上传图片
|
||||||
*/
|
*/
|
||||||
suspend fun tryUploadResourceByNt(
|
private suspend fun internalTryUploadResourceByNt(
|
||||||
chatType: Int,
|
chatType: Int,
|
||||||
elementType: Int,
|
elementType: Int,
|
||||||
resources: ArrayList<File>,
|
resources: ArrayList<File>,
|
||||||
@ -276,8 +279,9 @@ internal object NtV2RichMediaSvc: BaseSvc() {
|
|||||||
}
|
}
|
||||||
val contact = when(chatType) {
|
val contact = when(chatType) {
|
||||||
MsgConstant.KCHATTYPEC2C -> MessageHelper.generateContact(chatType, TicketSvc.getUin())
|
MsgConstant.KCHATTYPEC2C -> MessageHelper.generateContact(chatType, TicketSvc.getUin())
|
||||||
else -> Contact(chatType, GROUP_PIC_UPLOAD_TO, GROUP_PIC_UPLOAD_TO)
|
else -> Contact(chatType, fetchGroupResUploadTo(), null)
|
||||||
}
|
}
|
||||||
|
LogCenter.log(contact.toString())
|
||||||
val result = mutableListOf<CommonFileInfo>()
|
val result = mutableListOf<CommonFileInfo>()
|
||||||
withTimeoutOrNull(timeout) {
|
withTimeoutOrNull(timeout) {
|
||||||
suspendCancellableCoroutine {
|
suspendCancellableCoroutine {
|
||||||
@ -297,10 +301,12 @@ internal object NtV2RichMediaSvc: BaseSvc() {
|
|||||||
message = ArrayList(messages),
|
message = ArrayList(messages),
|
||||||
uniseq = uniseq.qqMsgId
|
uniseq = uniseq.qqMsgId
|
||||||
) { _, _ ->
|
) { _, _ ->
|
||||||
val kernelService = NTServiceFetcher.kernelService
|
if (contact.chatType == MsgConstant.KCHATTYPEGROUP && contact.peerUid == "100000000") {
|
||||||
val sessionService = kernelService.wrapperSession
|
val kernelService = NTServiceFetcher.kernelService
|
||||||
val msgService = sessionService.msgService
|
val sessionService = kernelService.wrapperSession
|
||||||
msgService.deleteMsg(contact, arrayListOf(uniseq.qqMsgId), null)
|
val msgService = sessionService.msgService
|
||||||
|
msgService.deleteMsg(contact, arrayListOf(uniseq.qqMsgId), null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
it.invokeOnCancellation {
|
it.invokeOnCancellation {
|
||||||
RichMediaUploadHandler.removeListener(uniseq.qqMsgId)
|
RichMediaUploadHandler.removeListener(uniseq.qqMsgId)
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
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<String> = arrayOf("cover", "desc")
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AdaptShareInfo(
|
||||||
|
val result: String
|
||||||
|
)
|
||||||
|
}
|
@ -1,33 +0,0 @@
|
|||||||
package moe.fuqiuluo.shamrock.remote.action.handlers
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.json.JsonElement
|
|
||||||
import moe.fuqiuluo.qqinterface.servlet.ark.ArkMsgSvc
|
|
||||||
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("sign_ark_message")
|
|
||||||
internal object SignArkMessage: IActionHandler() {
|
|
||||||
override suspend fun internalHandle(session: ActionSession): String {
|
|
||||||
val json = if(session.isString("json")) session.getString("json")
|
|
||||||
else session.getJsonElement("json").toString()
|
|
||||||
return invoke(json, session.echo)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend operator fun invoke(json: String, echo: JsonElement = EmptyJsonString): String {
|
|
||||||
/*
|
|
||||||
ArkMsgSvc.tryShareJsonMessage(json).onSuccess {
|
|
||||||
return ok(SignArkMessageResult(it), echo = echo)
|
|
||||||
}.onFailure {
|
|
||||||
return error(it.message ?: it.toString(), echo)
|
|
||||||
}*/
|
|
||||||
return logic("logic error", echo)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class SignArkMessageResult(
|
|
||||||
val result: String
|
|
||||||
)
|
|
||||||
}
|
|
@ -31,16 +31,21 @@ import moe.fuqiuluo.shamrock.tools.jsonArray
|
|||||||
import moe.fuqiuluo.shamrock.tools.respond
|
import moe.fuqiuluo.shamrock.tools.respond
|
||||||
|
|
||||||
fun Routing.messageAction() {
|
fun Routing.messageAction() {
|
||||||
route("/sign_ark_message") {
|
route("/adapt_share_json") {
|
||||||
get {
|
get {
|
||||||
val json = fetchGetOrThrow("json")
|
val cover = fetchGetOrThrow("cover")
|
||||||
call.respondText(SignArkMessage(json), ContentType.Application.Json)
|
val desc = fetchGetOrThrow("desc")
|
||||||
|
val url = fetchGetOrNull("url") ?: ""
|
||||||
|
call.respondText(AdaptShareJson(cover, desc, url), ContentType.Application.Json)
|
||||||
}
|
}
|
||||||
post {
|
post {
|
||||||
val json = if (isJsonData() && (isJsonObject("json") || isJsonArray("json")))
|
//val json = if (isJsonData() && (isJsonObject("json") || isJsonArray("json")))
|
||||||
fetchPostJsonElement("json").toString()
|
// fetchPostJsonElement("json").toString()
|
||||||
else fetchPostOrThrow("json")
|
//else fetchPostOrThrow("json")
|
||||||
call.respondText(SignArkMessage(json), ContentType.Application.Json)
|
val cover = fetchPostOrThrow("cover")
|
||||||
|
val desc = fetchPostOrThrow("desc")
|
||||||
|
val url = fetchPostOrNull("url") ?: ""
|
||||||
|
call.respondText(AdaptShareJson(cover, desc, url), ContentType.Application.Json)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,6 +102,13 @@ fun Routing.messageAction() {
|
|||||||
call.respondText(GetMsg(msgHash), ContentType.Application.Json)
|
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()) {
|
route("/(send_msg|send_message)".toRegex()) {
|
||||||
get {
|
get {
|
||||||
val msgType = fetchGetOrThrow("message_type")
|
val msgType = fetchGetOrThrow("message_type")
|
||||||
|
@ -24,13 +24,6 @@ fun Routing.testAction() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
getOrPost("/createUidFromTinyId") {
|
getOrPost("/createUidFromTinyId") {
|
||||||
val selfId = fetchOrThrow("selfId").toLong()
|
val selfId = fetchOrThrow("selfId").toLong()
|
||||||
val peerId = fetchOrThrow("peerId")
|
val peerId = fetchOrThrow("peerId")
|
||||||
|
@ -2,8 +2,10 @@ package moe.fuqiuluo.shamrock.remote.service.config
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
|
import de.robv.android.xposed.XposedBridge
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.transfile.NtV2RichMediaSvc
|
||||||
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.tools.GlobalJson5
|
import moe.fuqiuluo.shamrock.tools.GlobalJson5
|
||||||
@ -17,7 +19,7 @@ internal object ShamrockConfig {
|
|||||||
if (it.exists()) it.delete()
|
if (it.exists()) it.delete()
|
||||||
it.mkdirs()
|
it.mkdirs()
|
||||||
}
|
}
|
||||||
private val Config = kotlin.runCatching {
|
private val config = kotlin.runCatching {
|
||||||
GlobalJson5.decodeFromString<ServiceConfig>(ConfigDir.resolve("config.json").also {
|
GlobalJson5.decodeFromString<ServiceConfig>(ConfigDir.resolve("config.json").also {
|
||||||
if (!it.exists()) it.writeText("{}")
|
if (!it.exists()) it.writeText("{}")
|
||||||
}.readText())
|
}.readText())
|
||||||
@ -32,40 +34,21 @@ internal object ShamrockConfig {
|
|||||||
return mmkv.getBoolean("isInit", false)
|
return mmkv.getBoolean("isInit", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateConfig(config: ServiceConfig = Config) {
|
private fun updateConfig(config: ServiceConfig = this.config) {
|
||||||
ConfigDir.resolve("config.json").writeText(GlobalJson5.encodeToString(config))
|
ConfigDir.resolve("config.json").writeText(GlobalJson5.encodeToString(config))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateConfig(intent: Intent) {
|
fun updateConfig(intent: Intent) {
|
||||||
val mmkv = MMKVFetcher.mmkvWithId("shamrock_config")
|
|
||||||
mmkv.apply {
|
mmkv.apply {
|
||||||
if (!intent.getBooleanExtra("disable_auto_sync_setting", false)) {
|
if (!intent.getBooleanExtra("disable_auto_sync_setting", false)) {
|
||||||
putBoolean(
|
putBoolean("tablet", intent.getBooleanExtra("tablet", false)) // 强制平板模式
|
||||||
"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(
|
putBoolean("http", intent.getBooleanExtra("http", false)) // HTTP回调开关
|
||||||
"http",
|
putString("http_addr", intent.getStringExtra("http_addr")) // WebHook回调地址
|
||||||
intent.getBooleanExtra("http", false)
|
putBoolean("ws_client", intent.getBooleanExtra("ws_client", false)) // 被动WS开关
|
||||||
) // HTTP回调开关
|
putBoolean("use_cqcode", intent.getBooleanExtra("use_cqcode", false)) // 使用CQ码
|
||||||
putString(
|
putBoolean("inject_packet", intent.getBooleanExtra("inject_packet", false)) // 拦截无用包
|
||||||
"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)) // 调试模式
|
putBoolean("debug", intent.getBooleanExtra("debug", false)) // 调试模式
|
||||||
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")) // 证书密码
|
||||||
@ -78,22 +61,24 @@ internal object ShamrockConfig {
|
|||||||
putBoolean("enable_sync_msg_as_sent_msg", intent.getBooleanExtra("enable_sync_msg_as_sent_msg", false)) // 推送同步消息
|
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("forbid_useless_process", intent.getBooleanExtra("forbid_useless_process", false)) // 禁用QQ生成无用进程
|
||||||
putBoolean("enable_old_bdh", intent.getBooleanExtra("enable_old_bdh", false)) // 启用旧版BDH
|
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.defaultToken = intent.getStringExtra("token")
|
||||||
Config.antiTrace = intent.getBooleanExtra("anti_qq_trace", true)
|
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(
|
||||||
address = "0.0.0.0",
|
address = "0.0.0.0",
|
||||||
port = wsPort,
|
port = wsPort,
|
||||||
) else Config.activeWebSocket?.also {
|
) else config.activeWebSocket?.also {
|
||||||
it.port = wsPort
|
it.port = wsPort
|
||||||
}
|
}
|
||||||
Config.passiveWebSocket = intent.getStringExtra("ws_addr")?.split(",", "|", ",")?.filter { address ->
|
config.passiveWebSocket = intent.getStringExtra("ws_addr")?.split(",", "|", ",")?.filter { address ->
|
||||||
address.isNotBlank() && (address.startsWith("ws://") || address.startsWith("wss://"))
|
address.isNotBlank() && (address.startsWith("ws://") || address.startsWith("wss://"))
|
||||||
}?.map {
|
}?.map {
|
||||||
ConnectionConfig(address = it)
|
ConnectionConfig(address = it)
|
||||||
}?.toMutableList()
|
}?.toMutableList()
|
||||||
|
|
||||||
putBoolean("isInit", true)
|
putBoolean("isInit", true)
|
||||||
}
|
}
|
||||||
if (!intent.getBooleanExtra("disable_auto_sync_setting", false)) {
|
if (!intent.getBooleanExtra("disable_auto_sync_setting", false)) {
|
||||||
@ -101,23 +86,77 @@ internal object ShamrockConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
private val mmkv: MMKV
|
||||||
get() = MMKVFetcher.mmkvWithId("shamrock_config")
|
get() = MMKVFetcher.mmkvWithId("shamrock_config")
|
||||||
|
|
||||||
|
fun getUpResGroup(): String {
|
||||||
|
return mmkv.getString("up_res_group", "") ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
fun aliveReply(): Boolean {
|
fun aliveReply(): Boolean {
|
||||||
return mmkv.getBoolean("alive_reply", false)
|
return mmkv.getBoolean("alive_reply", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun allowTempSession(): Boolean {
|
fun allowTempSession(): Boolean {
|
||||||
return Config.allowTempSession
|
return config.allowTempSession
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getGroupMsgRule(): GroupRule? {
|
fun getGroupMsgRule(): GroupRule? {
|
||||||
return Config.rules?.groupRule
|
return config.rules?.groupRule
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPrivateRule(): PrivateRule? {
|
fun getPrivateRule(): PrivateRule? {
|
||||||
return Config.rules?.privateRule
|
return config.rules?.privateRule
|
||||||
}
|
}
|
||||||
|
|
||||||
fun enableSyncMsgAsSentMsg(): Boolean {
|
fun enableSyncMsgAsSentMsg(): Boolean {
|
||||||
@ -137,7 +176,7 @@ internal object ShamrockConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getWebSocketClientAddress(): List<ConnectionConfig> {
|
fun getWebSocketClientAddress(): List<ConnectionConfig> {
|
||||||
return Config.passiveWebSocket ?: emptyList()
|
return config.passiveWebSocket ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openWebSocket(): Boolean {
|
fun openWebSocket(): Boolean {
|
||||||
@ -145,11 +184,11 @@ internal object ShamrockConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getActiveWebSocketConfig(): ConnectionConfig? {
|
fun getActiveWebSocketConfig(): ConnectionConfig? {
|
||||||
return Config.activeWebSocket
|
return config.activeWebSocket
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getToken(): String {
|
fun getToken(): String {
|
||||||
return Config.defaultToken ?: ""
|
return config.defaultToken ?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
fun useCQ(): Boolean {
|
fun useCQ(): Boolean {
|
||||||
@ -236,7 +275,7 @@ internal object ShamrockConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun isAntiTrace(): Boolean {
|
fun isAntiTrace(): Boolean {
|
||||||
return Config.antiTrace
|
return config.antiTrace
|
||||||
}
|
}
|
||||||
|
|
||||||
fun allowShell(): Boolean {
|
fun allowShell(): Boolean {
|
||||||
|
@ -81,7 +81,6 @@ class AntiDetection: IAction {
|
|||||||
} else if (!pref.getBoolean("super_anti", false)) {
|
} else if (!pref.getBoolean("super_anti", false)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//System.loadLibrary("clover")
|
|
||||||
NativeLoader.load("clover")
|
NativeLoader.load("clover")
|
||||||
val env = XposedEntry.hasEnv()
|
val env = XposedEntry.hasEnv()
|
||||||
val injected = XposedEntry.injected()
|
val injected = XposedEntry.injected()
|
||||||
|
@ -70,6 +70,7 @@ class PullConfig: IAction {
|
|||||||
DataRequester.request("init", onFailure = {
|
DataRequester.request("init", onFailure = {
|
||||||
if (!ShamrockConfig.isInit()) {
|
if (!ShamrockConfig.isInit()) {
|
||||||
ctx.toast("请启动Shamrock主进程以初始化服务,进程将退出。")
|
ctx.toast("请启动Shamrock主进程以初始化服务,进程将退出。")
|
||||||
|
ShamrockConfig.putDefaultSettings()
|
||||||
thread {
|
thread {
|
||||||
Thread.sleep(3000)
|
Thread.sleep(3000)
|
||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package moe.fuqiuluo.shamrock.xposed.loader
|
package moe.fuqiuluo.shamrock.xposed.loader
|
||||||
|
|
||||||
object LuoClassloader: ClassLoader() {
|
internal object LuoClassloader: ClassLoader() {
|
||||||
lateinit var hostClassLoader: ClassLoader
|
lateinit var hostClassLoader: ClassLoader
|
||||||
lateinit var ctxClassLoader: ClassLoader
|
lateinit var ctxClassLoader: ClassLoader
|
||||||
|
|
||||||
|
internal val moduleLoader: ClassLoader = LuoClassloader::class.java.classLoader!!
|
||||||
|
|
||||||
fun load(name: String): Class<*>? {
|
fun load(name: String): Class<*>? {
|
||||||
return kotlin.runCatching {
|
return kotlin.runCatching {
|
||||||
loadClass(name)
|
loadClass(name)
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
package moe.fuqiuluo.shamrock.xposed.loader
|
package moe.fuqiuluo.shamrock.xposed.loader
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
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.loader.tmpnativehelper.moduleClassLoader
|
import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader.moduleLoader
|
||||||
import mqq.app.MobileQQ
|
import mqq.app.MobileQQ
|
||||||
import oicq.wlogin_sdk.tools.MD5
|
import oicq.wlogin_sdk.tools.MD5
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -20,28 +19,24 @@ internal object NativeLoader {
|
|||||||
return externalLibPath.resolve("libffmpegkit.so").exists()
|
return externalLibPath.resolve("libffmpegkit.so").exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val isEmu: Boolean
|
private val isEmu: Boolean = runCatching {
|
||||||
get() {
|
if (Build.SUPPORTED_ABIS.any { it.contains("x86") }) {
|
||||||
if (Build.SUPPORTED_ABIS.any { it.contains("x86") }) {
|
XposedBridge.log("[Shamrock] 通过SUPPORTED_ABIS检测到 Android x86")
|
||||||
XposedBridge.log("[Shamrock] 通过SUPPORTED_ABIS检测到 Android x86")
|
return@runCatching true
|
||||||
return true
|
|
||||||
}
|
|
||||||
return try {
|
|
||||||
val clazz = Class.forName("dalvik.system.VMRuntime")
|
|
||||||
val method = clazz.getDeclaredMethod("getRuntime")
|
|
||||||
val runtime = method.invoke(null)
|
|
||||||
val field = clazz.getDeclaredField("vmInstructionSet")
|
|
||||||
field.isAccessible = true
|
|
||||||
val instructionSet = field.get(runtime) as String
|
|
||||||
if ( instructionSet.contains("x86") ) {
|
|
||||||
XposedBridge.log("[Shamrock] 反射检测到 Android x86")
|
|
||||||
true
|
|
||||||
} else false
|
|
||||||
} catch (e: Exception) {
|
|
||||||
XposedBridge.log("[Shamrock] $e")
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
val clazz = Class.forName("dalvik.system.VMRuntime")
|
||||||
|
val method = clazz.getDeclaredMethod("getRuntime")
|
||||||
|
val runtime = method.invoke(null)
|
||||||
|
val field = clazz.getDeclaredField("vmInstructionSet")
|
||||||
|
field.isAccessible = true
|
||||||
|
val instructionSet = field.get(runtime) as String
|
||||||
|
if (instructionSet.contains("x86") ) {
|
||||||
|
XposedBridge.log("[Shamrock] 反射检测到 Android x86")
|
||||||
|
true
|
||||||
|
} else false
|
||||||
|
}.onFailure {
|
||||||
|
XposedBridge.log("[Shamrock] ${it.stackTraceToString()}")
|
||||||
|
}.getOrElse { false }
|
||||||
|
|
||||||
private fun getLibFilePath(name: String): String {
|
private fun getLibFilePath(name: String): String {
|
||||||
return if (isEmu) "lib/x86_64/lib${name}.so" else "lib/arm64-v8a/lib$name.so"
|
return if (isEmu) "lib/x86_64/lib${name}.so" else "lib/arm64-v8a/lib$name.so"
|
||||||
@ -50,48 +45,61 @@ internal object NativeLoader {
|
|||||||
/**
|
/**
|
||||||
* 使目标进程可以使用来自模块的库
|
* 使目标进程可以使用来自模块的库
|
||||||
*/
|
*/
|
||||||
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
|
||||||
fun load(name: String) {
|
fun load(name: String) {
|
||||||
try {
|
try {
|
||||||
if (name == "shamrock" || name == "clover") {
|
if (name == "shamrock"
|
||||||
onLoadByCopiedLibrary(name, getCtx())
|
|| (name == "clover" && isEmu)
|
||||||
} else {
|
) {
|
||||||
val sourceFile = externalLibPath.resolve("lib$name.so")
|
onLoadByResource(name)
|
||||||
val soFile = MobileQQ.getContext().filesDir.parentFile!!.resolve("txlib").resolve("lib$name.so")
|
} else if (!onLoadByAbsolutePath(name)) {
|
||||||
if (!soFile.exists()) {
|
onLoadByExternalFile(name)
|
||||||
if (!sourceFile.exists()) {
|
|
||||||
LogCenter.log("LoadExternalLibrary(name = $name) failed, file not exists.", level = Level.ERROR)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
sourceFile.copyTo(soFile)
|
|
||||||
Runtime.getRuntime().exec("chmod 755 ${soFile.absolutePath}").waitFor()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LogCenter.log("LoadExternalLibrary(name = $name)")
|
|
||||||
System.load(soFile.absolutePath)
|
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
XposedBridge.log(e)
|
XposedBridge.log(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun getCtx() = MobileQQ.getContext()
|
private fun onLoadByAbsolutePath(name: String): Boolean {
|
||||||
|
val context = MobileQQ.getContext()
|
||||||
|
val packageManager = context.packageManager
|
||||||
|
val applicationInfo = packageManager.getApplicationInfo("moe.fuqiuluo.shamrock.hided", 0)
|
||||||
|
val file = File(applicationInfo.nativeLibraryDir)
|
||||||
|
LogCenter.log("LoadLibrary(name = $name)")
|
||||||
|
loadLibrary(file.resolve("lib$name.so").also {
|
||||||
|
if (!it.exists()) {
|
||||||
|
LogCenter.log("LoadLibrary(name = $name) failed, file not exists.", level = Level.ERROR)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}.absolutePath, false)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
private fun onLoadByExternalFile(name: String) {
|
||||||
private fun onLoadByCopiedLibrary(name: String, context: Context) {
|
val sourceFile = externalLibPath.resolve("lib$name.so")
|
||||||
val soDir = File(context.filesDir, "SM_LIBS")
|
val soFile = MobileQQ.getContext().filesDir.parentFile!!
|
||||||
if (soDir.isFile) {
|
.resolve("txlib").resolve("lib$name.so")
|
||||||
soDir.delete()
|
if (!soFile.exists()) {
|
||||||
}
|
if (!sourceFile.exists()) {
|
||||||
if (!soDir.exists()) {
|
LogCenter.log("LoadExternalLibrary(name = $name) failed, file not exists.", level = Level.ERROR)
|
||||||
soDir.mkdirs()
|
return
|
||||||
|
} else {
|
||||||
|
sourceFile.copyTo(soFile)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
LogCenter.log("LoadExternalLibrary(name = $name)")
|
||||||
|
loadLibrary(soFile.absolutePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onLoadByResource(name: String) {
|
||||||
|
val soDir = File(MobileQQ.getContext().filesDir, "SM_LIBS")
|
||||||
|
if (soDir.isFile) soDir.delete()
|
||||||
|
if (!soDir.exists()) soDir.mkdirs()
|
||||||
val soPath = getLibFilePath(name)
|
val soPath = getLibFilePath(name)
|
||||||
val soFile = File(soDir, name)
|
val soFile = File(soDir, name)
|
||||||
fun reloadSo(tmp: File? = null) {
|
fun reloadSo(tmp: File? = null) {
|
||||||
LogCenter.log("SO文件大小不一致或不存在,正在重新加载", Level.INFO)
|
LogCenter.log("SO文件大小不一致或不存在,正在重新加载", Level.INFO)
|
||||||
soFile.delete()
|
soFile.delete()
|
||||||
if (tmp == null) moduleClassLoader.getResourceAsStream(soPath).use { origin ->
|
if (tmp == null) moduleLoader.getResourceAsStream(soPath).use { origin ->
|
||||||
soFile.outputStream().use { origin.copyTo(it) }
|
soFile.outputStream().use { origin.copyTo(it) }
|
||||||
} else tmp.renameTo(soFile)
|
} else tmp.renameTo(soFile)
|
||||||
}
|
}
|
||||||
@ -99,10 +107,10 @@ internal object NativeLoader {
|
|||||||
if (!soFile.exists()) {
|
if (!soFile.exists()) {
|
||||||
reloadSo()
|
reloadSo()
|
||||||
} else {
|
} else {
|
||||||
val tmpSoFile = soFile.resolve("$name.tmp").also { file ->
|
val tmpSoFile = File(soDir, "$name.tmp").also { file ->
|
||||||
if (file.exists()) file.delete()
|
if (file.exists()) file.delete()
|
||||||
file.outputStream().use {
|
file.outputStream().use {
|
||||||
moduleClassLoader.getResourceAsStream(soPath).use { origin ->
|
moduleLoader.getResourceAsStream(soPath).use { origin ->
|
||||||
origin.copyTo(it)
|
origin.copyTo(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,18 +119,20 @@ internal object NativeLoader {
|
|||||||
it != MD5.getFileMD5(tmpSoFile)
|
it != MD5.getFileMD5(tmpSoFile)
|
||||||
}) {
|
}) {
|
||||||
reloadSo(tmpSoFile)
|
reloadSo(tmpSoFile)
|
||||||
}
|
} else { tmpSoFile.delete() }
|
||||||
}
|
|
||||||
try {
|
|
||||||
System.load(soFile.absolutePath)
|
|
||||||
LogCenter.log("加载SO文件成功 -> ${soFile.path}", Level.INFO)
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
LogCenter.log(e.toString(), Level.WARN)
|
|
||||||
throw e
|
|
||||||
}
|
}
|
||||||
|
loadLibrary(soFile.absolutePath)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
LogCenter.log(e.toString(), Level.WARN)
|
LogCenter.log(e.toString(), Level.ERROR)
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
||||||
|
private fun loadLibrary(path: String, autoChmod: Boolean = true) {
|
||||||
|
if (autoChmod) Runtime.getRuntime()
|
||||||
|
.exec("chmod 755 $path").waitFor()
|
||||||
|
System.load(path)
|
||||||
|
LogCenter.log({ "加载SO文件成功 -> $path" }, Level.DEBUG)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
@file:JvmName("ModuleInfo")
|
|
||||||
package moe.fuqiuluo.shamrock.xposed.loader.tmpnativehelper
|
|
||||||
|
|
||||||
import moe.fuqiuluo.shamrock.xposed.loader.LuoClassloader
|
|
||||||
|
|
||||||
val moduleClassLoader: ClassLoader = LuoClassloader::class.java.classLoader!!
|
|
Reference in New Issue
Block a user