Merge branch 'master' of github.com:whitechi73/OpenShamrock

This commit is contained in:
ikechan8370 2023-11-21 23:39:26 +08:00
commit 1593d973a0
15 changed files with 148 additions and 67 deletions

13
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,13 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: shamrock320 # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@ -24,7 +24,7 @@ android {
minSdk = 24
targetSdk = 33
versionCode = (System.currentTimeMillis() / 1000).toInt()
versionName = "1.0.5-dev" + gitCommitHash()
versionName = "1.0.6-dev" + gitCommitHash()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@ -66,6 +66,7 @@ android {
create("app") {
dimension = "mode"
ndk {
println("Full architecture and full compilation.")
abiFilters.add("arm64-v8a")
abiFilters.add("x86_64")
}
@ -73,12 +74,14 @@ android {
create("arm64") {
dimension = "mode"
ndk {
println("Full compilation of arm64 architecture")
abiFilters.add("arm64-v8a")
}
}
create("x64") {
dimension = "mode"
ndk {
println("Full compilation of x64 architecture")
abiFilters.add("x86_64")
}
}

View File

@ -1,7 +1,5 @@
package com.tencent.qqnt.kernel.nativeinterface;
/* compiled from: P */
/* loaded from: classes2.dex */
public final class GroupFileCommonResult {
String clientWording;
int retCode;
@ -29,8 +27,6 @@ public final class GroupFileCommonResult {
}
public GroupFileCommonResult(int i2, String str, String str2) {
this.retMsg = "";
this.clientWording = "";
this.retCode = i2;
this.retMsg = str;
this.clientWording = str2;

View File

@ -1,5 +1,5 @@
package com.tencent.qqnt.kernel.nativeinterface;
public interface IDeleteGroupFileCallback {
void onResult(int i2, String str, DeleteGroupFileResult deleteGroupFileResult);
void onResult(int code, String why, DeleteGroupFileResult result);
}

View File

@ -9,7 +9,7 @@ public interface IKernelRichMediaService {
void cancelTransferTask(Contact contact, ArrayList<Long> arrayList, ArrayList<Integer> arrayList2, IOperateTransferInfoCallback iOperateTransferInfoCallback);
void deleteGroupFile(long j2, String str, int i2, IDeleteGroupFileCallback iDeleteGroupFileCallback);
void deleteGroupFile(long groupCode, String fileUid, int bizId, IDeleteGroupFileCallback cb);
void deleteTransferInfo(Contact contact, ArrayList<Long> arrayList, IOperateTransferInfoCallback iOperateTransferInfoCallback);

View File

@ -11,6 +11,7 @@ import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.proto.protobufOf
import moe.fuqiuluo.shamrock.utils.PlatformUtils
@ -37,9 +38,9 @@ internal abstract class BaseSvc {
}
suspend fun sendOidbAW(cmd: String, cmdId: Int, serviceId: Int, data: ByteArray, trpc: Boolean = false, timeout: Long = 5000L): ByteArray? {
val seq = MsfCore.getNextSeq()
return withTimeoutOrNull(timeout) {
suspendCoroutine { continuation ->
val seq = MsfCore.getNextSeq()
suspendCancellableCoroutine { continuation ->
GlobalScope.launch(Dispatchers.Default) {
DynamicReceiver.register(IPCRequest(cmd, seq) {
val buffer = it.getByteArrayExtra("buffer")!!
@ -49,13 +50,16 @@ internal abstract class BaseSvc {
if (trpc) sendTrpcOidb(cmd, cmdId, serviceId, data, seq)
else sendOidb(cmd, cmdId, serviceId, data, seq)
}
}.also {
if (it == null)
DynamicReceiver.unregister(seq)
}?.copyOf()
}
suspend fun sendBufferAW(cmd: String, isPb: Boolean, data: ByteArray, timeout: Long = 5000L): ByteArray? {
val seq = MsfCore.getNextSeq()
return withTimeoutOrNull<ByteArray?>(timeout) {
suspendCoroutine { continuation ->
val seq = MsfCore.getNextSeq()
suspendCancellableCoroutine { continuation ->
GlobalScope.launch(Dispatchers.Default) {
DynamicReceiver.register(IPCRequest(cmd, seq) {
val buffer = it.getByteArrayExtra("buffer")!!
@ -64,6 +68,9 @@ internal abstract class BaseSvc {
sendBuffer(cmd, isPb, data, seq)
}
}
}.also {
if (it == null)
DynamicReceiver.unregister(seq)
}?.copyOf()
}

View File

@ -1,7 +1,12 @@
package moe.fuqiuluo.qqinterface.servlet
import com.tencent.mobileqq.pb.ByteStringMicro
import com.tencent.qqnt.kernel.nativeinterface.DeleteGroupFileResult
import com.tencent.qqnt.kernel.nativeinterface.GroupFileCommonResult
import com.tencent.qqnt.kernel.nativeinterface.IDeleteGroupFileCallback
import io.ktor.util.Deflate
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import moe.fuqiuluo.proto.protobufOf
@ -12,8 +17,11 @@ import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY
import moe.fuqiuluo.shamrock.tools.slice
import moe.fuqiuluo.shamrock.tools.toHexString
import moe.fuqiuluo.shamrock.utils.DeflateTools
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
import tencent.im.oidb.cmd0x6d8.oidb_0x6d8
import tencent.im.oidb.oidb_sso
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
internal object FileSvc: BaseSvc() {
fun createFileFolder(groupId: String, folderName: String) {
@ -38,8 +46,23 @@ internal object FileSvc: BaseSvc() {
}
fun deleteGroupFile(groupId: String, bizId: Int, fileUid: String) {
/*
val kernelService = NTServiceFetcher.kernelService
val sessionService = kernelService.wrapperSession
val richMediaService = sessionService.richMediaService
val result = withTimeoutOrNull(3000L) {
suspendCancellableCoroutine {
richMediaService.deleteGroupFile(groupId.toLong(), fileUid, bizId) { code, _, result ->
it.resume(code to result.result)
}
}
}
return if (result == null) Result.failure(RuntimeException("delete group file timeout")) else Result.success(result)*/
// 调用QQ内部实现会导致闪退
sendOidb("OidbSvc.0x6d6_3", 1750, 3, protobufOf(
4 to mapOf(
4 to mapOf(
1 to groupId.toLong(),
2 to 3,
3 to bizId,

View File

@ -143,13 +143,15 @@ internal sealed class MessageElemConverter: IMessageConvert {
element: MsgElement
): MessageSegment {
val video = element.videoElement
val md5 = video.fileName.split(".")[0]
return MessageSegment(
type = "video",
data = hashMapOf(
"file" to video.fileName,
"url" to when(chatType) {
MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupVideoDownUrl("0", video.fileName, video.fileUuid)
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", video.fileName, video.fileUuid)
MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid)
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", md5, video.fileUuid)
else -> unknownChatType(chatType)
}
).also {

View File

@ -63,24 +63,30 @@ internal object RichProtoSvc: BaseSvc() {
suspend fun getC2CFileDownUrl(
fileId: String,
subId: String,
retryCnt: Int = 0
): String {
val uid = ContactHelper.getUidByUinAsync(app.currentUin.toLong())
val buffer = sendOidbAW("OidbSvcTrpcTcp.0xe37_1200", 3639, 1200, protobufOf(
val buffer = sendOidbAW("OidbSvc.0xe37_1200", 3639, 1200, protobufOf(
1 to 1200,
2 to 1 /* QRoute.api(IAudioHelperApi::class.java).genDebugSeq().toInt() */, /* seq */
14 to mapOf(
10 to uid,
10 to app.longAccountUin,
20 to fileId,
30 to 2, /* ver */
60 to subId,
601 to 0
),
101 to 3,
101 to 3, // uint32_business_id
102 to 104, /* client_type */
200 to 1, /* url_type */
99999 to 90200 to 1
).toByteArray(), trpc = true)
200 to 1, /* uint32_flag_support_mediaplatform */
99999 to mapOf(
90200 to 1 // uint32_download_url_type
)
).toByteArray())
if (buffer == null) {
if (retryCnt < 3) {
return getC2CFileDownUrl(fileId, subId, retryCnt + 1)
}
return ""
} else {
val body = oidb_sso.OIDBSSOPkg()

View File

@ -14,10 +14,30 @@ internal object DeleteGroupFile: IActionHandler() {
return invoke(groupId, fileId, busid, session.echo)
}
/*
suspend operator fun invoke(
groupId: String,
fileId: String,
bizId: Int,
echo: JsonElement = EmptyJsonString
): String {
val result = FileSvc.deleteGroupFile(groupId, bizId, fileId)
if(result.isFailure) {
return error(result.exceptionOrNull()?.message ?: "删除群文件失败", echo)
}
val commonResult = result.getOrThrow()
if (commonResult.first != 0 || commonResult.second.retCode != 0) {
return error(commonResult.second.clientWording, echo)
}
return ok("成功", echo)
}
*/
operator fun invoke(groupId: String, fileId: String, bizId: Int, echo: JsonElement = EmptyJsonString): String {
FileSvc.deleteGroupFile(groupId, bizId, fileId)
return ok("成功", echo)
}
override val requiredParams: Array<String> = arrayOf("group_id", "file_id", "busid")
override fun path(): String = "delete_group_file"
}

View File

@ -17,10 +17,12 @@ import moe.fuqiuluo.shamrock.remote.config.ECHO_KEY
import moe.fuqiuluo.shamrock.remote.entries.EmptyObject
import moe.fuqiuluo.shamrock.remote.entries.IndexData
import moe.fuqiuluo.shamrock.remote.entries.Status
import moe.fuqiuluo.shamrock.tools.EmptyJsonObject
import moe.fuqiuluo.shamrock.tools.fetchOrNull
import moe.fuqiuluo.shamrock.tools.fetchOrThrow
import moe.fuqiuluo.shamrock.tools.fetchPostJsonElement
import moe.fuqiuluo.shamrock.tools.fetchPostJsonObject
import moe.fuqiuluo.shamrock.tools.fetchPostJsonObjectOrNull
import moe.fuqiuluo.shamrock.tools.isJsonArray
import moe.fuqiuluo.shamrock.tools.isJsonObject
import moe.fuqiuluo.shamrock.tools.isJsonString
@ -55,7 +57,7 @@ fun Routing.echoVersion() {
}
call.attributes.put(ECHO_KEY, echo)
val params = fetchPostJsonObject("params")
val params = fetchPostJsonObjectOrNull("params") ?: EmptyJsonObject
val handler = ActionManager[action]
if (handler == null) {

View File

@ -69,8 +69,8 @@ internal class WebSocketService(host: String, port: Int): WebSocketTransmitServl
}
val path = URI.create(handshake.resourceDescriptor).path
if (path != "/api") {
pushMetaLifecycle()
eventReceivers.add(conn)
pushMetaLifecycle()
}
LogCenter.log({ "WSServer连接(${conn.remoteSocketAddress.address.hostAddress}:${conn.remoteSocketAddress.port}$path)" }, Level.WARN)
}

View File

@ -97,7 +97,7 @@ internal abstract class WebSocketTransmitServlet(
}
val action = actionObject["action"].asString
val echo = actionObject["echo"] ?: EmptyJsonString
val params = actionObject["params"].asJsonObject
val params = actionObject["params"].asJsonObjectOrNull ?: EmptyJsonObject
val handler = ActionManager[action]
handler?.handle(ActionSession(params, echo))

View File

@ -55,7 +55,7 @@ internal object PrimitiveListener {
) return
val msgType = pb[1, 2, 1].asInt
var subType = 0
if (pb.has(1, 2, 3)) {
if (pb.has(1, 2, 3) && pb.has(1, 2, 2)) {
subType = pb[1, 2, 2].asInt
}
val msgTime = pb[1, 2, 6].asLong

View File

@ -20,13 +20,10 @@ import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.jsonObject
import moe.fuqiuluo.shamrock.helper.ParamsException
import io.ktor.http.HttpMethod
import io.ktor.http.decodeURLPart
import io.ktor.http.parseUrlEncodedParameters
import io.ktor.server.request.httpMethod
import io.ktor.server.routing.route
import kotlinx.serialization.json.JsonElement
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.remote.entries.CommonResult
import moe.fuqiuluo.shamrock.remote.entries.EmptyObject
import moe.fuqiuluo.shamrock.remote.entries.Status
@ -38,9 +35,9 @@ import moe.fuqiuluo.shamrock.remote.entries.Status
annotation class ShamrockDsl
private val isJsonKey = AttributeKey<Boolean>("isJson")
private val jsonKey = AttributeKey<JsonObject>("paramsJson")
private val partsKey = AttributeKey<Parameters>("paramsParts")
private val keyIsJson = AttributeKey<Boolean>("isJson")
private val keyJsonObject = AttributeKey<JsonObject>("paramsJson")
private val keyParts = AttributeKey<Parameters>("paramsParts")
suspend fun ApplicationCall.fetch(key: String): String {
val isPost = request.httpMethod == HttpMethod.Post
@ -94,23 +91,23 @@ fun ApplicationCall.isJsonData(): Boolean {
}
suspend fun ApplicationCall.fetchPostOrNull(key: String): String? {
if (attributes.contains(jsonKey)) {
return attributes[jsonKey][key].asStringOrNull
if (attributes.contains(keyJsonObject)) {
return attributes[keyJsonObject][key].asStringOrNull
}
if (attributes.contains(partsKey)) {
return attributes[partsKey][key]
if (attributes.contains(keyParts)) {
return attributes[keyParts][key]
}
return kotlin.runCatching {
if (isJsonData()) {
Json.parseToJsonElement(receiveText()).jsonObject.also {
attributes.put(jsonKey, it)
attributes.put(isJsonKey, true)
attributes.put(keyJsonObject, it)
attributes.put(keyIsJson, true)
}[key].asStringOrNull
} else if (
ContentType.Application.FormUrlEncoded == request.contentType()
) {
receiveParameters().also {
attributes.put(partsKey, it)
attributes.put(keyParts, it)
}[key]
} else {
receiveTextAsUnknown(key)
@ -124,13 +121,13 @@ private suspend fun ApplicationCall.receiveTextAsUnknown(key: String): String? {
return receiveText().let { text ->
if (text.startsWith("{") && text.endsWith("}")) {
Json.parseToJsonElement(text).jsonObject.also {
attributes.put(jsonKey, it)
attributes.put(isJsonKey, true)
attributes.put(keyJsonObject, it)
attributes.put(keyIsJson, true)
}[key].asStringOrNull
} else {
text.parseUrlEncodedParameters().also {
attributes.put(partsKey, it)
attributes.put(isJsonKey, false)
attributes.put(keyParts, it)
attributes.put(keyIsJson, false)
}[key]
}
} // receiveText
@ -170,17 +167,17 @@ suspend fun PipelineContext<Unit, ApplicationCall>.fetchPostOrThrow(key: String)
}
fun PipelineContext<Unit, ApplicationCall>.isJsonData(): Boolean {
return ContentType.Application.Json == call.request.contentType() || call.attributes[isJsonKey]
return ContentType.Application.Json == call.request.contentType() || (keyIsJson in call.attributes && call.attributes[keyIsJson])
}
suspend fun PipelineContext<Unit, ApplicationCall>.isJsonString(key: String): Boolean {
if (!isJsonData()) return true
val data = if (call.attributes.contains(jsonKey)) {
call.attributes[jsonKey]
val data = if (keyJsonObject in call.attributes) {
call.attributes[keyJsonObject]
} else {
Json.parseToJsonElement(call.receiveText()).jsonObject.also {
call.attributes.put(jsonKey, it)
call.attributes.put(isJsonKey, true)
call.attributes.put(keyJsonObject, it)
call.attributes.put(keyIsJson, true)
}
}
return data[key] is JsonPrimitive
@ -188,11 +185,11 @@ suspend fun PipelineContext<Unit, ApplicationCall>.isJsonString(key: String): Bo
suspend fun PipelineContext<Unit, ApplicationCall>.isJsonObject(key: String): Boolean {
if (!isJsonData()) return false
val data = if (call.attributes.contains(jsonKey)) {
call.attributes[jsonKey]
val data = if (call.attributes.contains(keyJsonObject)) {
call.attributes[keyJsonObject]
} else {
Json.parseToJsonElement(call.receiveText()).jsonObject.also {
call.attributes.put(jsonKey, it)
call.attributes.put(keyJsonObject, it)
}
}
return data[key] is JsonObject
@ -200,56 +197,68 @@ suspend fun PipelineContext<Unit, ApplicationCall>.isJsonObject(key: String): Bo
suspend fun PipelineContext<Unit, ApplicationCall>.isJsonArray(key: String): Boolean {
if (!isJsonData()) return false
val data = if (call.attributes.contains(jsonKey)) {
call.attributes[jsonKey]
val data = if (call.attributes.contains(keyJsonObject)) {
call.attributes[keyJsonObject]
} else {
Json.parseToJsonElement(call.receiveText()).jsonObject.also {
call.attributes.put(jsonKey, it)
call.attributes.put(keyJsonObject, it)
}
}
return data[key] is JsonArray
}
suspend fun PipelineContext<Unit, ApplicationCall>.fetchPostJsonString(key: String): String {
val data = if (call.attributes.contains(jsonKey)) {
call.attributes[jsonKey]
val data = if (call.attributes.contains(keyJsonObject)) {
call.attributes[keyJsonObject]
} else {
Json.parseToJsonElement(call.receiveText()).jsonObject.also {
call.attributes.put(jsonKey, it)
call.attributes.put(keyJsonObject, it)
}
}
return data[key].asString
}
suspend fun PipelineContext<Unit, ApplicationCall>.fetchPostJsonElement(key: String): JsonElement {
val data = if (call.attributes.contains(jsonKey)) {
call.attributes[jsonKey]
val data = if (call.attributes.contains(keyJsonObject)) {
call.attributes[keyJsonObject]
} else {
Json.parseToJsonElement(call.receiveText()).jsonObject.also {
call.attributes.put(jsonKey, it)
call.attributes.put(keyJsonObject, it)
}
}
return data[key]!!
}
suspend fun PipelineContext<Unit, ApplicationCall>.fetchPostJsonObject(key: String): JsonObject {
val data = if (call.attributes.contains(jsonKey)) {
call.attributes[jsonKey]
val data = if (call.attributes.contains(keyJsonObject)) {
call.attributes[keyJsonObject]
} else {
Json.parseToJsonElement(call.receiveText()).jsonObject.also {
call.attributes.put(jsonKey, it)
call.attributes.put(keyJsonObject, it)
}
}
return data[key].asJsonObject
}
suspend fun PipelineContext<Unit, ApplicationCall>.fetchPostJsonArray(key: String): JsonArray {
val data = if (call.attributes.contains(jsonKey)) {
call.attributes[jsonKey]
suspend fun PipelineContext<Unit, ApplicationCall>.fetchPostJsonObjectOrNull(key: String): JsonObject? {
val data = if (call.attributes.contains(keyJsonObject)) {
call.attributes[keyJsonObject]
} else {
Json.parseToJsonElement(call.receiveText()).jsonObject.also {
call.attributes.put(jsonKey, it)
call.attributes.put(isJsonKey, true)
call.attributes.put(keyJsonObject, it)
}
}
return data[key].asJsonObjectOrNull
}
suspend fun PipelineContext<Unit, ApplicationCall>.fetchPostJsonArray(key: String): JsonArray {
val data = if (call.attributes.contains(keyJsonObject)) {
call.attributes[keyJsonObject]
} else {
Json.parseToJsonElement(call.receiveText()).jsonObject.also {
call.attributes.put(keyJsonObject, it)
call.attributes.put(keyIsJson, true)
}
}
return data[key].asJsonArray