mirror of
https://github.com/whitechi73/OpenShamrock.git
synced 2024-08-14 13:12:17 +08:00
Shamrock
: Support du chat de groupe et transfert de messages
This commit is contained in:
parent
d28c6dc820
commit
bc967cf926
@ -173,24 +173,19 @@ dependencies {
|
|||||||
implementation("io.coil-kt:coil:2.4.0")
|
implementation("io.coil-kt:coil:2.4.0")
|
||||||
implementation("io.coil-kt:coil-compose:2.4.0")
|
implementation("io.coil-kt:coil-compose:2.4.0")
|
||||||
|
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
|
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-io-jvm:0.1.16")
|
implementation("org.jetbrains.kotlinx:kotlinx-io-jvm:0.1.16")
|
||||||
implementation("io.ktor:ktor-server-core:2.3.3")
|
|
||||||
implementation("io.ktor:ktor-server-host-common:2.3.3")
|
val ktorVersion = "2.3.3"
|
||||||
implementation("io.ktor:ktor-server-status-pages:2.3.3")
|
implementation("io.ktor:ktor-server-core:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-server-netty:2.3.3")
|
implementation("io.ktor:ktor-server-host-common:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.3")
|
implementation("io.ktor:ktor-server-status-pages:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-server-content-negotiation:2.3.3")
|
implementation("io.ktor:ktor-server-netty:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-client-core:2.3.3")
|
implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-client-cio:2.3.3")
|
implementation("io.ktor:ktor-client-core:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-client-content-negotiation:2.3.3")
|
implementation("io.ktor:ktor-client-cio:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.3")
|
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
|
||||||
// useless
|
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
|
||||||
//implementation ("com.maxkeppeler.sheets-compose-dialogs:core:1.2.0")
|
//implementation("io.ktor:ktor-serialization-kotlinx-protobuf:$ktorVersion")
|
||||||
//implementation ("com.maxkeppeler.sheets-compose-dialogs:info:1.2.0")
|
|
||||||
//implementation ("com.maxkeppeler.sheets-compose-dialogs:input:1.2.0")
|
|
||||||
//implementation ("com.maxkeppeler.sheets-compose-dialogs:list:1.2.0")
|
|
||||||
//implementation ("com.maxkeppeler.sheets-compose-dialogs:state:1.2.0")
|
|
||||||
|
|
||||||
implementation(project(":xposed"))
|
implementation(project(":xposed"))
|
||||||
|
|
||||||
|
@ -1,6 +1,50 @@
|
|||||||
package com.tencent.mobileqq.pb;
|
package com.tencent.mobileqq.pb;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
public class MessageMicro<T extends MessageMicro<T>> {
|
public class MessageMicro<T extends MessageMicro<T>> {
|
||||||
|
public static final class FieldMap {
|
||||||
|
private Object[] defaultValues;
|
||||||
|
private Field[] fields;
|
||||||
|
private int[] tags;
|
||||||
|
|
||||||
|
FieldMap(int[] iArr, String[] strArr, Object[] objArr, Class<?> cls) {
|
||||||
|
this.tags = iArr;
|
||||||
|
this.defaultValues = objArr;
|
||||||
|
this.fields = new Field[iArr.length];
|
||||||
|
for (int i2 = 0; i2 < iArr.length; i2++) {
|
||||||
|
try {
|
||||||
|
this.fields[i2] = cls.getField(strArr[i2]);
|
||||||
|
} catch (Exception e2) {
|
||||||
|
e2.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear(MessageMicro<?> messageMicro) {
|
||||||
|
}
|
||||||
|
|
||||||
|
<U extends MessageMicro<U>> void copyFields(U u, U u2) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Field get(int i2) {
|
||||||
|
int binarySearch = Arrays.binarySearch(this.tags, i2);
|
||||||
|
if (binarySearch < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.fields[binarySearch];
|
||||||
|
}
|
||||||
|
|
||||||
|
int getSerializedSize(MessageMicro<?> messageMicro) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FieldMap initFieldMap(int[] iArr, String[] strArr, Object[] objArr, Class<?> cls) {
|
||||||
|
return new FieldMap(iArr, strArr, objArr, cls);
|
||||||
|
}
|
||||||
|
|
||||||
public final T mergeFrom(byte[] bArr) {
|
public final T mergeFrom(byte[] bArr) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,10 @@ public interface IKernelMsgService {
|
|||||||
|
|
||||||
void getMultiMsg(Contact contact, long rootMsgId, long parentMsgId, IGetMultiMsgCallback cb);
|
void getMultiMsg(Contact contact, long rootMsgId, long parentMsgId, IGetMultiMsgCallback cb);
|
||||||
|
|
||||||
|
void multiForwardMsg(ArrayList<MultiMsgInfo> arrayList, Contact from, Contact to, IOperateCallback cb);
|
||||||
|
|
||||||
|
void setAllC2CAndGroupMsgRead(IOperateCallback cb);
|
||||||
|
|
||||||
void clearMsgRecords(Contact contact, IClearMsgRecordsCallback cb);
|
void clearMsgRecords(Contact contact, IClearMsgRecordsCallback cb);
|
||||||
|
|
||||||
String createUidFromTinyId(long j2, long j3);
|
String createUidFromTinyId(long j2, long j3);
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package com.tencent.qqnt.kernel.nativeinterface;
|
package com.tencent.qqnt.kernel.nativeinterface;
|
||||||
|
|
||||||
/* compiled from: P */
|
|
||||||
/* loaded from: classes2.dex */
|
|
||||||
public final class MultiMsgInfo {
|
public final class MultiMsgInfo {
|
||||||
long msgId;
|
long msgId;
|
||||||
String senderShowName;
|
String senderShowName;
|
||||||
@ -21,8 +19,6 @@ public final class MultiMsgInfo {
|
|||||||
return "MultiMsgInfo{msgId=" + this.msgId + ",senderShowName=" + this.senderShowName + ",}";
|
return "MultiMsgInfo{msgId=" + this.msgId + ",senderShowName=" + this.senderShowName + ",}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultiMsgInfo(long j2, String str) {
|
public MultiMsgInfo(long msgId, String showName) {
|
||||||
this.msgId = j2;
|
|
||||||
this.senderShowName = str;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,7 @@ dependencies {
|
|||||||
implementation("io.ktor:ktor-client-cio:$ktorVersion")
|
implementation("io.ktor:ktor-client-cio:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
|
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
|
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
|
||||||
|
//implementation("io.ktor:ktor-serialization-kotlinx-protobuf:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-network-tls-certificates:$ktorVersion")
|
implementation("io.ktor:ktor-network-tls-certificates:$ktorVersion")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
@file:OptIn(ExperimentalSerializationApi::class)
|
||||||
|
package moe.fuqiuluo.qqinterface.entries
|
||||||
|
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.Serializable
|
@ -101,7 +101,7 @@ internal abstract class BaseSvc {
|
|||||||
app.sendToService(to)
|
app.sendToService(to)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendBuffer(cmd: String, isPb: Boolean, buffer: ByteArray, seq: Int) {
|
fun sendBuffer(cmd: String, isPb: Boolean, buffer: ByteArray, seq: Int = MsfCore.getNextSeq()) {
|
||||||
val toServiceMsg = ToServiceMsg("mobileqq.service", app.currentUin, cmd)
|
val toServiceMsg = ToServiceMsg("mobileqq.service", app.currentUin, cmd)
|
||||||
toServiceMsg.putWupBuffer(buffer)
|
toServiceMsg.putWupBuffer(buffer)
|
||||||
toServiceMsg.addAttribute("req_pb_protocol_flag", isPb)
|
toServiceMsg.addAttribute("req_pb_protocol_flag", isPb)
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
@file:OptIn(DelicateCoroutinesApi::class)
|
||||||
|
|
||||||
package moe.fuqiuluo.qqinterface.servlet
|
package moe.fuqiuluo.qqinterface.servlet
|
||||||
|
|
||||||
import com.tencent.mobileqq.qroute.QRoute
|
import com.tencent.mobileqq.qroute.QRoute
|
||||||
@ -9,9 +11,15 @@ import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
|
|||||||
import com.tencent.qqnt.kernel.nativeinterface.TempChatGameSession
|
import com.tencent.qqnt.kernel.nativeinterface.TempChatGameSession
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.TempChatPrepareInfo
|
import com.tencent.qqnt.kernel.nativeinterface.TempChatPrepareInfo
|
||||||
import com.tencent.qqnt.msg.api.IMsgService
|
import com.tencent.qqnt.msg.api.IMsgService
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import kotlinx.coroutines.time.withTimeoutOrNull
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
import kotlinx.serialization.json.JsonArray
|
import kotlinx.serialization.json.JsonArray
|
||||||
|
import moe.fuqiuluo.proto.protobufOf
|
||||||
import moe.fuqiuluo.shamrock.helper.ContactHelper
|
import moe.fuqiuluo.shamrock.helper.ContactHelper
|
||||||
import moe.fuqiuluo.shamrock.helper.Level
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
@ -19,6 +27,7 @@ import moe.fuqiuluo.shamrock.helper.MessageHelper
|
|||||||
import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY
|
import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY
|
||||||
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
|
||||||
|
import java.util.UUID
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
@ -183,6 +192,44 @@ internal object MsgSvc: BaseSvc() {
|
|||||||
return MessageHelper.sendMessageWithoutMsgId(chatType, peedId, message, MessageCallback(peedId, 0), fromId)
|
return MessageHelper.sendMessageWithoutMsgId(chatType, peedId, message, MessageCallback(peedId, 0), fromId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getMultiMsg(resId: String): Result<List<MsgRecord>> {
|
||||||
|
val kernelService = NTServiceFetcher.kernelService
|
||||||
|
val sessionService = kernelService.wrapperSession
|
||||||
|
val msgService = sessionService.msgService
|
||||||
|
val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, TicketSvc.getUin())
|
||||||
|
|
||||||
|
val content = "{\"app\":\"com.tencent.multimsg\",\"config\":{\"autosize\":1,\"forward\":1,\"round\":1,\"type\":\"normal\",\"width\":300},\"desc\":\"[聊天记录]\",\"extra\":\"\",\"meta\":{\"detail\":{\"news\":[{\"text\":\"Shamrock: 这是条假消息!\"}],\"resid\":\"$resId\",\"source\":\"聊天记录\",\"summary\":\"转发消息\",\"uniseq\":\"${UUID.randomUUID()}\"}},\"prompt\":\"[聊天记录]\",\"ver\":\"0.0.0.5\",\"view\":\"contact\"}"
|
||||||
|
val msgId = PacketSvc.fakeSelfRecvJsonMsg(msgService, content)
|
||||||
|
if (msgId < 0) {
|
||||||
|
return Result.failure(Exception("获取合并转发消息ID失败"))
|
||||||
|
}
|
||||||
|
val msgList = withTimeoutOrNull(5000L) {
|
||||||
|
suspendCancellableCoroutine<ArrayList<MsgRecord>> {
|
||||||
|
val job = GlobalScope.launch {
|
||||||
|
var hasResult = false
|
||||||
|
while (!hasResult) {
|
||||||
|
msgService.getMultiMsg(contact, msgId, msgId) { code, why, msgList ->
|
||||||
|
if (code == 0) {
|
||||||
|
it.resume(msgList)
|
||||||
|
hasResult = true
|
||||||
|
} else {
|
||||||
|
LogCenter.log("获取合并转发消息失败: $code($why): $msgId", Level.ERROR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delay(200)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.invokeOnCancellation {
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ?: return Result.failure(Exception("获取合并转发消息失败"))
|
||||||
|
|
||||||
|
//msgService.deleteMsg(contact, arrayListOf(msgId), null)
|
||||||
|
|
||||||
|
return Result.success(msgList)
|
||||||
|
}
|
||||||
|
|
||||||
class MessageCallback(
|
class MessageCallback(
|
||||||
private val peerId: String,
|
private val peerId: String,
|
||||||
var msgHash: Int
|
var msgHash: Int
|
||||||
|
@ -0,0 +1,98 @@
|
|||||||
|
package moe.fuqiuluo.qqinterface.servlet
|
||||||
|
|
||||||
|
import com.tencent.mobileqq.msf.core.MsfCore
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.Contact
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.IKernelMsgService
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||||
|
import io.ktor.utils.io.core.BytePacketBuilder
|
||||||
|
import io.ktor.utils.io.core.readBytes
|
||||||
|
import io.ktor.utils.io.core.writeFully
|
||||||
|
import io.ktor.utils.io.core.writeInt
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
|
import moe.fuqiuluo.proto.protobufOf
|
||||||
|
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
||||||
|
import moe.fuqiuluo.shamrock.remote.action.handlers.GetHistoryMsg
|
||||||
|
import moe.fuqiuluo.shamrock.remote.service.listener.AioListener
|
||||||
|
import moe.fuqiuluo.shamrock.tools.broadcast
|
||||||
|
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
||||||
|
import mqq.app.MobileQQ
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.random.Random
|
||||||
|
import kotlin.random.nextInt
|
||||||
|
import kotlin.random.nextLong
|
||||||
|
|
||||||
|
internal object PacketSvc: BaseSvc() {
|
||||||
|
/**
|
||||||
|
* 伪造收到Json卡片消息
|
||||||
|
*/
|
||||||
|
suspend fun fakeSelfRecvJsonMsg(msgService: IKernelMsgService, content: String): Long {
|
||||||
|
return fakeReceiveSelfMsg(msgService) { arrayOf(
|
||||||
|
mapOf(
|
||||||
|
51 to 1 to (byteArrayOf(1) + DeflateTools.compress(content.toByteArray()))
|
||||||
|
)
|
||||||
|
) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun fakeReceiveSelfMsg(msgService: IKernelMsgService, builder: () -> Array<Map<*, *>>): Long {
|
||||||
|
val latestMsg = withTimeoutOrNull(3000) {
|
||||||
|
suspendCancellableCoroutine {
|
||||||
|
msgService.getMsgs(Contact(MsgConstant.KCHATTYPEC2C, app.currentUid, ""), 0L, 1, true) { code, why, msgs ->
|
||||||
|
it.resume(GetHistoryMsg.GetMsgResult(code, why, msgs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}?.data?.firstOrNull()
|
||||||
|
val msgSeq = (latestMsg?.msgSeq ?: 0) + 1
|
||||||
|
fakeReceive("trpc.msg.olpush.OlPushService.MsgPush", 10000, protobufOf(
|
||||||
|
1 to mapOf(
|
||||||
|
1 to mapOf(
|
||||||
|
1 to app.currentUin.toLong(),
|
||||||
|
2 to app.currentUid,
|
||||||
|
3 to 1001,
|
||||||
|
5 to app.currentUin.toLong(),
|
||||||
|
6 to app.currentUid
|
||||||
|
),
|
||||||
|
2 to mapOf(
|
||||||
|
1 to 166,
|
||||||
|
3 to 11,
|
||||||
|
4 to msgSeq,
|
||||||
|
5 to msgSeq,
|
||||||
|
6 to (System.currentTimeMillis() / 1000).toInt(),
|
||||||
|
7 to 1,
|
||||||
|
11 to msgSeq,
|
||||||
|
12 to msgService.getMsgUniqueId(System.currentTimeMillis()),
|
||||||
|
14 to msgSeq - 2,
|
||||||
|
28 to msgSeq
|
||||||
|
),
|
||||||
|
3 to 1 to 2 to builder()
|
||||||
|
)
|
||||||
|
).toByteArray())
|
||||||
|
return withTimeoutOrNull(5000L) {
|
||||||
|
suspendCancellableCoroutine {
|
||||||
|
AioListener.messageLessListenerMap[msgSeq] = {
|
||||||
|
it.resume(this.msgId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ?: -1L
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 伪造QQ收到某个包
|
||||||
|
*/
|
||||||
|
private fun fakeReceive(cmd: String, seq: Int, buffer: ByteArray) {
|
||||||
|
MobileQQ.getContext().broadcast("msf") {
|
||||||
|
putExtra("__cmd", "fake_packet")
|
||||||
|
putExtra("package_cmd", cmd)
|
||||||
|
putExtra("package_uin", app.currentUin)
|
||||||
|
putExtra("package_seq", seq)
|
||||||
|
val wupBuffer = BytePacketBuilder().apply {
|
||||||
|
writeInt(buffer.size + 4)
|
||||||
|
writeFully(buffer)
|
||||||
|
}.build()
|
||||||
|
putExtra("package_buffer", wupBuffer.readBytes())
|
||||||
|
wupBuffer.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,103 +0,0 @@
|
|||||||
package moe.fuqiuluo.qqinterface.servlet.msg
|
|
||||||
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
|
|
||||||
import moe.fuqiuluo.proto.ProtoUtils
|
|
||||||
import moe.fuqiuluo.proto.asUtf8String
|
|
||||||
import moe.fuqiuluo.proto.protobufOf
|
|
||||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
|
||||||
import moe.fuqiuluo.shamrock.tools.slice
|
|
||||||
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
|
||||||
|
|
||||||
internal object LongMsgHelper: BaseSvc() {
|
|
||||||
private const val GROUP_LONG_MSG_CMD = "trpc.group.long_msg_interface.MsgService.SsoSendLongMsg"
|
|
||||||
|
|
||||||
suspend fun uploadGroupMsg(groupId: String, msgs: List<MsgRecord>): String {
|
|
||||||
val reqBody = protobufOf(
|
|
||||||
2 to mapOf(
|
|
||||||
1 to 3,
|
|
||||||
2 to 2 to groupId,
|
|
||||||
3 to groupId.toLong(),
|
|
||||||
4 to DeflateTools.gzip(toGroupByteArray(msgs))
|
|
||||||
),
|
|
||||||
15 to mapOf(
|
|
||||||
1 to 4,
|
|
||||||
2 to 2,
|
|
||||||
3 to 9,
|
|
||||||
4 to 0
|
|
||||||
)
|
|
||||||
).toByteArray()
|
|
||||||
val buffer = sendBufferAW(GROUP_LONG_MSG_CMD, true, reqBody)
|
|
||||||
?: error("获取消息资源ID失败")
|
|
||||||
val pb = ProtoUtils.decodeFromByteArray(buffer.slice(4))
|
|
||||||
return pb[2, 3].asUtf8String
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun toGroupByteArray(msgs: List<MsgRecord>): ByteArray {
|
|
||||||
return protobufOf(
|
|
||||||
2 to mapOf(
|
|
||||||
1 to "MultiMsg",
|
|
||||||
2 to 1 to msgs.map { record ->
|
|
||||||
mapOf(
|
|
||||||
1 to mapOf(
|
|
||||||
2 to record.senderUid,
|
|
||||||
8 to mapOf(
|
|
||||||
1 to record.peerUin,
|
|
||||||
4 to record.sendNickName,
|
|
||||||
5 to 2
|
|
||||||
)
|
|
||||||
),
|
|
||||||
2 to mapOf(
|
|
||||||
1 to 82,
|
|
||||||
4 to record.msgRandom,
|
|
||||||
5 to record.msgSeq,
|
|
||||||
6 to record.msgTime,
|
|
||||||
7 to 1,
|
|
||||||
8 to 0,
|
|
||||||
9 to 0,
|
|
||||||
15 to mapOf(
|
|
||||||
1 to 0,
|
|
||||||
2 to 0,
|
|
||||||
3 to 0,
|
|
||||||
4 to "",
|
|
||||||
5 to ""
|
|
||||||
)
|
|
||||||
),
|
|
||||||
3 to mapOf(
|
|
||||||
1 to 2 to (record.elements.map {
|
|
||||||
when (val type = it.elementType) {
|
|
||||||
MsgConstant.KELEMTYPETEXT -> mapOf(1 to 1 to it.textElement.content)
|
|
||||||
|
|
||||||
else -> error("不支持的合并转发消息类型: $type")
|
|
||||||
}
|
|
||||||
} as ArrayList<Any>).also {
|
|
||||||
it.add(0, mapOf(
|
|
||||||
37 to mapOf(
|
|
||||||
1 to 8,
|
|
||||||
16 to 0,
|
|
||||||
17 to 0,
|
|
||||||
19 to mapOf(
|
|
||||||
15 to 65536,
|
|
||||||
25 to 0,
|
|
||||||
30 to 0,
|
|
||||||
31 to 0,
|
|
||||||
34 to 0,
|
|
||||||
41 to 0,
|
|
||||||
52 to 64,
|
|
||||||
54 to 1,
|
|
||||||
55 to 0,
|
|
||||||
72 to 0,
|
|
||||||
73 to 1 to 0,
|
|
||||||
96 to 0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
).toByteArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -53,8 +53,8 @@ internal object MessageConvert {
|
|||||||
MsgConstant.KELEMTYPEREPLY to MessageElemConverter.ReplyConverter,
|
MsgConstant.KELEMTYPEREPLY to MessageElemConverter.ReplyConverter,
|
||||||
MsgConstant.KELEMTYPEGRAYTIP to MessageElemConverter.GrayTipsConverter,
|
MsgConstant.KELEMTYPEGRAYTIP to MessageElemConverter.GrayTipsConverter,
|
||||||
MsgConstant.KELEMTYPEFILE to MessageElemConverter.FileConverter,
|
MsgConstant.KELEMTYPEFILE to MessageElemConverter.FileConverter,
|
||||||
MsgConstant.KELEMTYPEMULTIFORWARD to MessageElemConverter.XmlMultiMsgConverter,
|
//MsgConstant.KELEMTYPEMULTIFORWARD to MessageElemConverter.XmlMultiMsgConverter,
|
||||||
MsgConstant.KELEMTYPESTRUCTLONGMSG to MessageElemConverter.XmlLongMsgConverter,
|
//MsgConstant.KELEMTYPESTRUCTLONGMSG to MessageElemConverter.XmlLongMsgConverter,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,22 @@ import moe.fuqiuluo.shamrock.tools.jsonArray
|
|||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
internal object MessageHelper {
|
internal object MessageHelper {
|
||||||
|
suspend fun sendMessageWithoutMsgId(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
message: String,
|
||||||
|
callback: IOperateCallback,
|
||||||
|
fromId: String = peerId
|
||||||
|
): Pair<Long, Int> {
|
||||||
|
val uniseq = generateMsgId(chatType)
|
||||||
|
val msg = messageArrayToMessageElements(chatType, uniseq.second, peerId, decodeCQCode(message)).also {
|
||||||
|
if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。")
|
||||||
|
}.second.filter {
|
||||||
|
it.elementType != -1
|
||||||
|
} as ArrayList<MsgElement>
|
||||||
|
return sendMessageWithoutMsgId(chatType, peerId, msg, callback, fromId)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun sendMessageWithoutMsgId(
|
suspend fun sendMessageWithoutMsgId(
|
||||||
chatType: Int,
|
chatType: Int,
|
||||||
peerId: String,
|
peerId: String,
|
||||||
@ -31,14 +47,31 @@ internal object MessageHelper {
|
|||||||
fromId: String = peerId
|
fromId: String = peerId
|
||||||
): Pair<Long, Int> {
|
): Pair<Long, Int> {
|
||||||
val uniseq = generateMsgId(chatType)
|
val uniseq = generateMsgId(chatType)
|
||||||
var nonMsg: Boolean
|
|
||||||
val msg = messageArrayToMessageElements(chatType, uniseq.second, peerId, message).also {
|
val msg = messageArrayToMessageElements(chatType, uniseq.second, peerId, message).also {
|
||||||
if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。")
|
if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。")
|
||||||
}.second.filter {
|
}.second.filter {
|
||||||
it.elementType != -1
|
it.elementType != -1
|
||||||
}.also {
|
} as ArrayList<MsgElement>
|
||||||
nonMsg = it.isEmpty()
|
return sendMessageWithoutMsgId(chatType, peerId, msg, callback, fromId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun sendMessageWithoutMsgId(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
message: ArrayList<MsgElement>,
|
||||||
|
callback: IOperateCallback,
|
||||||
|
fromId: String = peerId
|
||||||
|
): Pair<Long, Int> {
|
||||||
|
return sendMessageWithoutMsgId(generateContact(chatType, peerId, fromId), message, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendMessageWithoutMsgId(
|
||||||
|
contact: Contact,
|
||||||
|
message: ArrayList<MsgElement>,
|
||||||
|
callback: IOperateCallback
|
||||||
|
): Pair<Long, Int> {
|
||||||
|
val uniseq = generateMsgId(contact.chatType)
|
||||||
|
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) {
|
||||||
@ -46,9 +79,9 @@ internal object MessageHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
service.sendMsg(
|
service.sendMsg(
|
||||||
generateContact(chatType, peerId, fromId),
|
contact,
|
||||||
uniseq.second,
|
uniseq.second,
|
||||||
msg as ArrayList<MsgElement>,
|
message,
|
||||||
callback
|
callback
|
||||||
)
|
)
|
||||||
System.currentTimeMillis() to uniseq.first
|
System.currentTimeMillis() to uniseq.first
|
||||||
@ -57,6 +90,64 @@ internal object MessageHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun sendMessageWithMsgId(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
message: JsonArray,
|
||||||
|
callback: IOperateCallback,
|
||||||
|
fromId: String = peerId
|
||||||
|
): Pair<Long, Int> {
|
||||||
|
val uniseq = generateMsgId(chatType)
|
||||||
|
val msg = messageArrayToMessageElements(chatType, uniseq.second, peerId, message).also {
|
||||||
|
if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。")
|
||||||
|
}.second.filter {
|
||||||
|
it.elementType != -1
|
||||||
|
} as ArrayList<MsgElement>
|
||||||
|
val contact = generateContact(chatType, peerId, fromId)
|
||||||
|
val nonMsg: Boolean = message.isEmpty()
|
||||||
|
return if (!nonMsg) {
|
||||||
|
val service = QRoute.api(IMsgService::class.java)
|
||||||
|
if(callback is MsgSvc.MessageCallback) {
|
||||||
|
callback.msgHash = uniseq.first
|
||||||
|
}
|
||||||
|
|
||||||
|
service.sendMsg(
|
||||||
|
contact,
|
||||||
|
uniseq.second,
|
||||||
|
msg,
|
||||||
|
callback
|
||||||
|
)
|
||||||
|
uniseq.second to uniseq.first
|
||||||
|
} else {
|
||||||
|
uniseq.second to 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendMessageWithMsgId(
|
||||||
|
contact: Contact,
|
||||||
|
message: ArrayList<MsgElement>,
|
||||||
|
callback: IOperateCallback
|
||||||
|
): Pair<Long, Int> {
|
||||||
|
val uniseq = generateMsgId(contact.chatType)
|
||||||
|
val nonMsg: Boolean = message.isEmpty()
|
||||||
|
return if (!nonMsg) {
|
||||||
|
val service = QRoute.api(IMsgService::class.java)
|
||||||
|
if(callback is MsgSvc.MessageCallback) {
|
||||||
|
callback.msgHash = uniseq.first
|
||||||
|
}
|
||||||
|
|
||||||
|
service.sendMsg(
|
||||||
|
contact,
|
||||||
|
uniseq.second,
|
||||||
|
message,
|
||||||
|
callback
|
||||||
|
)
|
||||||
|
uniseq.second to uniseq.first
|
||||||
|
} else {
|
||||||
|
0L to 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun generateContact(chatType: Int, id: String, subId: String = ""): Contact {
|
suspend fun generateContact(chatType: Int, id: String, subId: String = ""): Contact {
|
||||||
val peerId = if (MsgConstant.KCHATTYPEC2C == chatType || MsgConstant.KCHATTYPETEMPC2CFROMGROUP == chatType) {
|
val peerId = if (MsgConstant.KCHATTYPEC2C == chatType || MsgConstant.KCHATTYPETEMPC2CFROMGROUP == chatType) {
|
||||||
ContactHelper.getUidByUinAsync(id.toLong())
|
ContactHelper.getUidByUinAsync(id.toLong())
|
||||||
|
@ -1,22 +1,58 @@
|
|||||||
package moe.fuqiuluo.shamrock.remote.action.handlers
|
package moe.fuqiuluo.shamrock.remote.action.handlers
|
||||||
|
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.msg.convert.MessageConvert
|
||||||
|
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
||||||
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
||||||
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
|
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
import moe.fuqiuluo.shamrock.remote.service.data.MessageDetail
|
||||||
|
import moe.fuqiuluo.shamrock.remote.service.data.MessageSender
|
||||||
|
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
|
||||||
|
|
||||||
internal object GetForwardMsg: IActionHandler() {
|
internal object GetForwardMsg: IActionHandler() {
|
||||||
override suspend fun internalHandle(session: ActionSession): String {
|
override suspend fun internalHandle(session: ActionSession): String {
|
||||||
val id = session.getString("id")
|
val id = session.getString("id")
|
||||||
|
return invoke(id, session.echo)
|
||||||
val kernelService = NTServiceFetcher.kernelService
|
|
||||||
val sessionService = kernelService.wrapperSession
|
|
||||||
val msgService = sessionService.msgService
|
|
||||||
|
|
||||||
//msgService.getMultiMsg()
|
|
||||||
|
|
||||||
return error("不支持实现,请提交ISSUE!", session.echo)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend operator fun invoke(
|
||||||
|
resId: String,
|
||||||
|
echo: JsonElement = EmptyJsonString
|
||||||
|
): String {
|
||||||
|
val result = MsgSvc.getMultiMsg(resId)
|
||||||
|
if (result.isFailure) {
|
||||||
|
return logic(result.exceptionOrNull().toString(), echo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok(data = GetForwardMsgResult(result.getOrThrow().map { msg ->
|
||||||
|
val msgHash = MessageHelper.generateMsgIdHash(msg.chatType, msg.msgId)
|
||||||
|
MessageDetail(
|
||||||
|
time = msg.msgTime.toInt(),
|
||||||
|
msgType = MessageHelper.obtainDetailTypeByMsgType(msg.chatType),
|
||||||
|
msgId = msgHash,
|
||||||
|
realId = msg.msgSeq.toInt(),
|
||||||
|
sender = MessageSender(
|
||||||
|
msg.senderUin, msg.sendNickName, "unknown", 0, msg.senderUid
|
||||||
|
),
|
||||||
|
message = MessageConvert.convertMessageRecordToMsgSegment(msg).map {
|
||||||
|
it.toJson()
|
||||||
|
},
|
||||||
|
peerId = msg.peerUin,
|
||||||
|
groupId = if (msg.chatType == MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0,
|
||||||
|
targetId = if (msg.chatType != MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0
|
||||||
|
)
|
||||||
|
}), echo = echo)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class GetForwardMsgResult(
|
||||||
|
@SerialName("messages") val msgs: List<MessageDetail>
|
||||||
|
)
|
||||||
|
|
||||||
override val requiredParams: Array<String> = arrayOf("id")
|
override val requiredParams: Array<String> = arrayOf("id")
|
||||||
|
|
||||||
override fun path(): String = "get_forward_msg"
|
override fun path(): String = "get_forward_msg"
|
||||||
|
@ -1,41 +1,117 @@
|
|||||||
package moe.fuqiuluo.shamrock.remote.action.handlers
|
package moe.fuqiuluo.shamrock.remote.action.handlers
|
||||||
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
|
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MultiMsgInfo
|
||||||
|
import kotlinx.atomicfu.atomic
|
||||||
|
import kotlinx.serialization.json.JsonArray
|
||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
||||||
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
||||||
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
|
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
|
||||||
import moe.fuqiuluo.shamrock.tools.asInt
|
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
import moe.fuqiuluo.qqinterface.servlet.msg.convert.toSegments
|
||||||
import moe.fuqiuluo.qqinterface.servlet.msg.LongMsgHelper
|
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.EmptyJsonString
|
||||||
|
import moe.fuqiuluo.shamrock.tools.asInt
|
||||||
|
import moe.fuqiuluo.shamrock.tools.asJsonObject
|
||||||
|
import moe.fuqiuluo.shamrock.tools.asString
|
||||||
|
import moe.fuqiuluo.shamrock.tools.json
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
||||||
|
|
||||||
internal object SendGroupForwardMsg: IActionHandler() {
|
internal object SendGroupForwardMsg: IActionHandler() {
|
||||||
override suspend fun internalHandle(session: ActionSession): String {
|
override suspend fun internalHandle(session: ActionSession): String {
|
||||||
val groupId = session.getLong("group_id")
|
val groupId = session.getString("group_id")
|
||||||
val hashList = session.getArrayOrNull("seqs")?.map { it.asInt }
|
if (session.isArray("messages")) {
|
||||||
if (hashList != null) {
|
val messages = session.getArray("messages")
|
||||||
val msgs = hashList.map {
|
return invoke(messages, groupId, session.echo)
|
||||||
MsgSvc.getMsg(it).getOrNull()
|
|
||||||
}
|
}
|
||||||
val resId = LongMsgHelper.uploadGroupMsg(groupId.toString(), msgs.filterNotNull())
|
return logic("未知格式合并转发消息", session.echo)
|
||||||
return ok(mapOf("res_id" to resId), 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()
|
||||||
|
|
||||||
return "xxx"
|
val msgs = message.map {
|
||||||
|
it.asJsonObject["data"].asJsonObject.let {
|
||||||
|
if (it.containsKey("content"))
|
||||||
|
MessageNode(it["name"].asString, it["content"])
|
||||||
|
else MessageIdNode(it["id"].asInt)
|
||||||
|
}
|
||||||
|
}.map {
|
||||||
|
if (it is MessageIdNode) {
|
||||||
|
val recordResult = MsgSvc.getMsg(it.id)
|
||||||
|
if (recordResult.isFailure) {
|
||||||
|
EmptyNode
|
||||||
|
} else {
|
||||||
|
val record = recordResult.getOrThrow()
|
||||||
|
MessageNode(
|
||||||
|
name = record.sendMemberName
|
||||||
|
.ifBlank { record.sendNickName }
|
||||||
|
.ifBlank { record.sendRemarkName }
|
||||||
|
.ifBlank { record.peerName },
|
||||||
|
content = record.toSegments().map { segment ->
|
||||||
|
segment.toJson()
|
||||||
|
}.json
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
it as MessageNode
|
||||||
|
}
|
||||||
|
}.filter {
|
||||||
|
it.content != null
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun invoke(msgs: List<MsgRecord>, echo: JsonElement = EmptyJsonString): String {
|
var forwardMsgCallback: (() -> Unit)? = null
|
||||||
if (msgs.isEmpty()) {
|
val availableMsgSize = atomic(0)
|
||||||
return logic("消息为空", echo)
|
val msgIds = msgs.map {
|
||||||
} else if (msgs.size > 100) {
|
it.name to MessageHelper.sendMessageWithMsgId(MsgConstant.KCHATTYPEC2C, selfUin, it.content!!.let { msg ->
|
||||||
return logic("消息数量过多", echo)
|
if (msg is JsonArray) msg else MessageHelper.decodeCQCode(msg.asString)
|
||||||
|
}, { _, _ ->
|
||||||
|
if (availableMsgSize.incrementAndGet() == msgs.size) {
|
||||||
|
forwardMsgCallback?.invoke()
|
||||||
|
}
|
||||||
|
}).first
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val from = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, selfUin)
|
||||||
|
val to = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, groupId)
|
||||||
TODO()
|
forwardMsgCallback = {
|
||||||
|
msgService.multiForwardMsg(ArrayList<MultiMsgInfo>(msgIds.size).apply {
|
||||||
|
msgIds.forEach { add(MultiMsgInfo(it.second, it.first)) }
|
||||||
|
}, 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"
|
override fun path(): String = "send_group_forward_msg"
|
||||||
|
|
||||||
|
class MessageIdNode(
|
||||||
|
val id: Int
|
||||||
|
): Node
|
||||||
|
open class MessageNode(
|
||||||
|
val name: String,
|
||||||
|
val content: JsonElement?
|
||||||
|
): Node
|
||||||
|
object EmptyNode: MessageNode("", null)
|
||||||
|
interface Node
|
||||||
}
|
}
|
@ -22,6 +22,11 @@ import moe.fuqiuluo.shamrock.tools.isJsonData
|
|||||||
import moe.fuqiuluo.shamrock.tools.isJsonString
|
import moe.fuqiuluo.shamrock.tools.isJsonString
|
||||||
|
|
||||||
fun Routing.messageAction() {
|
fun Routing.messageAction() {
|
||||||
|
getOrPost("/get_forward_msg") {
|
||||||
|
val id = fetchOrThrow("id")
|
||||||
|
call.respondText(GetForwardMsg(id))
|
||||||
|
}
|
||||||
|
|
||||||
getOrPost("/get_group_msg_history") {
|
getOrPost("/get_group_msg_history") {
|
||||||
val peerId = fetchOrThrow("group_id")
|
val peerId = fetchOrThrow("group_id")
|
||||||
val cnt = fetchOrNull("count")?.toInt() ?: 20
|
val cnt = fetchOrNull("count")?.toInt() ?: 20
|
||||||
|
@ -16,9 +16,13 @@ import moe.fuqiuluo.shamrock.remote.service.api.GlobalEventTransmitter
|
|||||||
import moe.fuqiuluo.shamrock.remote.service.data.push.MessageTempSource
|
import moe.fuqiuluo.shamrock.remote.service.data.push.MessageTempSource
|
||||||
import moe.fuqiuluo.shamrock.remote.service.data.push.PostType
|
import moe.fuqiuluo.shamrock.remote.service.data.push.PostType
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
import java.util.HashMap
|
import java.util.Collections
|
||||||
|
import kotlin.collections.HashMap
|
||||||
|
|
||||||
internal object AioListener: IKernelMsgListener {
|
internal object AioListener: IKernelMsgListener {
|
||||||
|
// 通过MSG SEQ临时监听器
|
||||||
|
internal val messageLessListenerMap = Collections.synchronizedMap(HashMap<Long, MsgRecord.() -> Unit>())
|
||||||
|
|
||||||
override fun onRecvMsg(msgList: ArrayList<MsgRecord>) {
|
override fun onRecvMsg(msgList: ArrayList<MsgRecord>) {
|
||||||
if (msgList.isEmpty()) return
|
if (msgList.isEmpty()) return
|
||||||
|
|
||||||
@ -33,6 +37,15 @@ internal object AioListener: IKernelMsgListener {
|
|||||||
try {
|
try {
|
||||||
if (record.chatType == MsgConstant.KCHATTYPEGUILD) return // TODO: 频道消息暂不处理
|
if (record.chatType == MsgConstant.KCHATTYPEGUILD) return // TODO: 频道消息暂不处理
|
||||||
|
|
||||||
|
messageLessListenerMap.firstNotNullOfOrNull {
|
||||||
|
if(it.key == record.msgSeq) it else null
|
||||||
|
}?.let {
|
||||||
|
it.value(record)
|
||||||
|
messageLessListenerMap.remove(it.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (record.msgSeq < 0) return
|
||||||
|
|
||||||
val msgHash = MessageHelper.generateMsgIdHash(record.chatType, record.msgId)
|
val msgHash = MessageHelper.generateMsgIdHash(record.chatType, record.msgId)
|
||||||
|
|
||||||
MessageHelper.saveMsgMapping(
|
MessageHelper.saveMsgMapping(
|
||||||
@ -48,6 +61,10 @@ internal object AioListener: IKernelMsgListener {
|
|||||||
val rawMsg = record.elements.toCQCode(record.chatType, record.peerUin.toString())
|
val rawMsg = record.elements.toCQCode(record.chatType, record.peerUin.toString())
|
||||||
if (rawMsg.isEmpty()) return
|
if (rawMsg.isEmpty()) return
|
||||||
|
|
||||||
|
//if (rawMsg.contains("forward")) {
|
||||||
|
// LogCenter.log(record.extInfoForUI.decodeToString(), Level.WARN)
|
||||||
|
//}
|
||||||
|
|
||||||
when (record.chatType) {
|
when (record.chatType) {
|
||||||
MsgConstant.KCHATTYPEGROUP -> {
|
MsgConstant.KCHATTYPEGROUP -> {
|
||||||
LogCenter.log("群消息(group = ${record.peerName}(${record.peerUin}), uin = ${record.senderUin}, id = $msgHash|${record.msgSeq}, msg = $rawMsg)")
|
LogCenter.log("群消息(group = ${record.peerName}(${record.peerUin}), uin = ${record.senderUin}, id = $msgHash|${record.msgSeq}, msg = $rawMsg)")
|
||||||
@ -63,7 +80,7 @@ internal object AioListener: IKernelMsgListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
MsgConstant.KCHATTYPEC2C -> {
|
MsgConstant.KCHATTYPEC2C -> {
|
||||||
LogCenter.log("私聊消息(private = ${record.senderUin}, id = $msgHash|${record.msgSeq}, msg = $rawMsg)")
|
LogCenter.log("私聊消息(private = ${record.senderUin}, id = [$msgHash | ${record.msgId} | ${record.msgSeq}], msg = $rawMsg)")
|
||||||
ShamrockConfig.getPrivateRule()?.let { rule ->
|
ShamrockConfig.getPrivateRule()?.let { rule ->
|
||||||
if (rule.black?.contains(record.peerUin) == true) return
|
if (rule.black?.contains(record.peerUin) == true) return
|
||||||
if (rule.white?.contains(record.peerUin) == false) return
|
if (rule.white?.contains(record.peerUin) == false) return
|
||||||
|
@ -8,6 +8,7 @@ import com.tencent.qphone.base.remote.ToServiceMsg
|
|||||||
import com.tencent.qphone.base.util.CodecWarpper
|
import com.tencent.qphone.base.util.CodecWarpper
|
||||||
import kotlinx.atomicfu.atomic
|
import kotlinx.atomicfu.atomic
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
||||||
import moe.fuqiuluo.shamrock.remote.service.PacketReceiver
|
import moe.fuqiuluo.shamrock.remote.service.PacketReceiver
|
||||||
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
||||||
import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY
|
import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY
|
||||||
@ -15,7 +16,10 @@ import moe.fuqiuluo.shamrock.tools.hookMethod
|
|||||||
import moe.fuqiuluo.shamrock.tools.slice
|
import moe.fuqiuluo.shamrock.tools.slice
|
||||||
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.utils.PlatformUtils
|
import moe.fuqiuluo.shamrock.xposed.helper.internal.DynamicReceiver
|
||||||
|
import moe.fuqiuluo.shamrock.xposed.helper.internal.IPCRequest
|
||||||
|
|
||||||
|
private const val MAGIC_APP_ID = 114514
|
||||||
|
|
||||||
internal class HookWrapperCodec: IAction {
|
internal class HookWrapperCodec: IAction {
|
||||||
private val IgnoredCmd = arrayOf(
|
private val IgnoredCmd = arrayOf(
|
||||||
@ -54,12 +58,12 @@ internal class HookWrapperCodec: IAction {
|
|||||||
val isInit = atomic(false)
|
val isInit = atomic(false)
|
||||||
CodecWarpper::class.java.hookMethod("init").after {
|
CodecWarpper::class.java.hookMethod("init").after {
|
||||||
if (isInit.value) return@after
|
if (isInit.value) return@after
|
||||||
hookReceive(it.thisObject.javaClass)
|
hookReceive(it.thisObject, it.thisObject.javaClass)
|
||||||
isInit.lazySet(true)
|
isInit.lazySet(true)
|
||||||
}
|
}
|
||||||
CodecWarpper::class.java.hookMethod("nativeOnReceData").before {
|
CodecWarpper::class.java.hookMethod("nativeOnReceData").before {
|
||||||
if (isInit.value) return@before
|
if (isInit.value) return@before
|
||||||
hookReceive(it.thisObject.javaClass)
|
hookReceive(it.thisObject, it.thisObject.javaClass)
|
||||||
isInit.lazySet(true)
|
isInit.lazySet(true)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -67,7 +71,26 @@ internal class HookWrapperCodec: IAction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hookReceive(thizClass: Class<*>) {
|
private fun hookReceive(thiz: Any, thizClass: Class<*>) {
|
||||||
|
val onResponse = thizClass.getDeclaredMethod("onResponse", Integer.TYPE, Any::class.java, Integer.TYPE)
|
||||||
|
//LogCenter.log("HookWrapperCodec: onResponse = $onResponse", Level.INFO)
|
||||||
|
DynamicReceiver.register("fake_packet", IPCRequest {
|
||||||
|
val uin = it.getStringExtra("package_uin")!!
|
||||||
|
val cmd = it.getStringExtra("package_cmd")!!
|
||||||
|
val seq = it.getIntExtra("package_seq", 0)
|
||||||
|
val buffer = it.getByteArrayExtra("package_buffer")!!
|
||||||
|
//LogCenter.log("伪造收包(cmd = $cmd)")
|
||||||
|
|
||||||
|
val from = FromServiceMsg()
|
||||||
|
from.requestSsoSeq = seq
|
||||||
|
from.putWupBuffer(buffer)
|
||||||
|
from.serviceCmd = cmd
|
||||||
|
from.appId = MAGIC_APP_ID
|
||||||
|
from.setMsgSuccess()
|
||||||
|
from.uin = uin
|
||||||
|
from.appSeq = seq
|
||||||
|
onResponse.invoke(thiz, 0, from, 0)
|
||||||
|
})
|
||||||
thizClass.hookMethod("onResponse").before {
|
thizClass.hookMethod("onResponse").before {
|
||||||
val from = it.args[1] as FromServiceMsg
|
val from = it.args[1] as FromServiceMsg
|
||||||
try {
|
try {
|
||||||
@ -88,7 +111,7 @@ internal class HookWrapperCodec: IAction {
|
|||||||
}
|
}
|
||||||
merge.BusiBuffVec.set(busiBufVec)
|
merge.BusiBuffVec.set(busiBufVec)
|
||||||
from.putWupBuffer(merge.toByteArray())
|
from.putWupBuffer(merge.toByteArray())
|
||||||
} else {
|
} else if (from.appId != MAGIC_APP_ID) {
|
||||||
if (from.serviceCmd in IgnoredCmd && ShamrockConfig.isInjectPacket()) {
|
if (from.serviceCmd in IgnoredCmd && ShamrockConfig.isInjectPacket()) {
|
||||||
from.serviceCmd = "ShamrockInjectedCmd"
|
from.serviceCmd = "ShamrockInjectedCmd"
|
||||||
from.putWupBuffer(EMPTY_BYTE_ARRAY)
|
from.putWupBuffer(EMPTY_BYTE_ARRAY)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user