From 87629666f210c8ad48772360763b0900934e4842 Mon Sep 17 00:00:00 2001 From: Simplxs Date: Thu, 22 Feb 2024 01:04:15 +0800 Subject: [PATCH] =?UTF-8?q?refactor=20send=5Fforward=5Fmsg(=E6=9A=82?= =?UTF-8?q?=E6=97=B6=E5=8F=AA=E6=94=AF=E6=8C=81=E6=94=B6=E5=8F=91=E6=96=87?= =?UTF-8?q?=E5=AD=97=E6=B6=88=E6=81=AF)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/protobuf/message/MessageContent.kt | 7 +- .../java/protobuf/message/MessageElement.kt | 2 +- .../fuqiuluo/qqinterface/servlet/MsgSvc.kt | 9 +- .../fuqiuluo/qqinterface/servlet/PacketSvc.kt | 2 +- .../messageelement/MessageElementConverter.kt | 142 +++++++++--------- .../msg/messageelement/MessageElementMaker.kt | 94 +++++++++++- .../msg/msgelement/MsgElementConverter.kt | 2 +- .../servlet/msg/msgelement/MsgElementMaker.kt | 12 +- .../action/handlers/SendForwardMessage.kt | 30 ++-- .../service/listener/PrimitiveListener.kt | 4 +- 10 files changed, 207 insertions(+), 97 deletions(-) diff --git a/protobuf/src/main/java/protobuf/message/MessageContent.kt b/protobuf/src/main/java/protobuf/message/MessageContent.kt index 8cf172ab..0868eddb 100644 --- a/protobuf/src/main/java/protobuf/message/MessageContent.kt +++ b/protobuf/src/main/java/protobuf/message/MessageContent.kt @@ -6,14 +6,15 @@ import kotlinx.serialization.protobuf.ProtoNumber @Serializable data class MessageContent( @ProtoNumber(1) val msgType: Int = Int.MIN_VALUE, - @ProtoNumber(2) val msgSubType: Int = Int.MIN_VALUE, + @ProtoNumber(2) val msgSubType: Int? = null, + @ProtoNumber(3) val u1: Int? = null, @ProtoNumber(4) val msgViaRandom: Long = Long.MIN_VALUE, - @ProtoNumber(5) val msgSeq: Long = Long.MIN_VALUE, + @ProtoNumber(5) val msgSeq_: Long? = null, @ProtoNumber(6) val msgTime: Long? = null, @ProtoNumber(7) val u2: Int? = null, @ProtoNumber(8) val u6: Int? = null, @ProtoNumber(9) val u7: Int? = null, - @ProtoNumber(11) val u3: Long? = null, + @ProtoNumber(11) val msgSeq: Long? = null, @ProtoNumber(12) val msgRandom: Long = Long.MIN_VALUE, @ProtoNumber(14) val u4: Long? = null, @ProtoNumber(15) val forwardHead: ForwardHead? = null, diff --git a/protobuf/src/main/java/protobuf/message/MessageElement.kt b/protobuf/src/main/java/protobuf/message/MessageElement.kt index 956dfaff..3da71016 100644 --- a/protobuf/src/main/java/protobuf/message/MessageElement.kt +++ b/protobuf/src/main/java/protobuf/message/MessageElement.kt @@ -9,5 +9,5 @@ data class MessageElement( @ProtoNumber(1) val text: TextElement? = null, @ProtoNumber(2) val face: FaceElement? = null, @ProtoNumber(51) val json: JsonElement? = null, - @ProtoNumber(53) val commElem: CommonElement? = null, + @ProtoNumber(53) val comm: CommonElement? = null, ) diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/MsgSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/MsgSvc.kt index baee49a9..fe869f17 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/MsgSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/MsgSvc.kt @@ -299,14 +299,15 @@ internal object MsgSvc : BaseSvc() { time = msg.content?.msgTime?.toInt() ?: 0, msgType = MessageHelper.obtainDetailTypeByMsgType(chatType), msgId = 0, // MessageHelper.generateMsgIdHash(chatType, msg.content!!.msgViaRandom), msgViaRandom 为空 - realId = msg.content!!.msgSeq.toInt(), + realId = msg.content!!.msgSeq?.toInt() ?: 0, sender = MessageSender( msg.head?.peer ?: 0, - msg.head?.groupInfo?.memberCard?.ifEmpty { msg.head?.forward?.friendName } ?: "", + msg.head?.groupInfo?.memberCard?.ifEmpty { msg.head?.forward?.friendName } + ?: msg.head?.forward?.friendName ?: "", "unknown", 0, - msg.head?.peerUid ?: "u_", - msg.head?.peerUid?: "u_" + msg.head?.peerUid ?: "", + msg.head?.peerUid ?: "" ), message = msg.body?.rich?.elements?.toSegments(chatType, msg.head?.peer.toString(), "0") ?.toListMap() ?: emptyList(), diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/PacketSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/PacketSvc.kt index 22122c04..646d6a7c 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/PacketSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/PacketSvc.kt @@ -66,7 +66,7 @@ internal object PacketSvc: BaseSvc() { msgViaRandom = msgSeq, msgTime = System.currentTimeMillis() / 1000, u2 = 1, - u3 = msgSeq, + msgSeq_ = msgSeq, msgRandom = msgService.getMsgUniqueId(System.currentTimeMillis()), u4 = msgSeq - 2, u5 = msgSeq diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/messageelement/MessageElementConverter.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/messageelement/MessageElementConverter.kt index 4583ce48..f45fa59b 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/messageelement/MessageElementConverter.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/messageelement/MessageElementConverter.kt @@ -6,6 +6,9 @@ import kotlinx.io.core.readUInt import moe.fuqiuluo.qqinterface.servlet.msg.MessageSegment import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.LogCenter +import moe.fuqiuluo.shamrock.utils.DeflateTools +import moe.fuqiuluo.shamrock.tools.asJsonObject +import moe.fuqiuluo.shamrock.tools.asString import protobuf.message.MessageElement @@ -23,6 +26,8 @@ internal suspend fun List.toSegments( 2 } else if (msg.json != null) { 51 + } else if (msg.comm != null) { + 53 } else throw UnsupportedOperationException("不支持的消息element类型:$msg") val converter = MessageElementConverter[elementType] @@ -45,13 +50,13 @@ internal typealias IMessageElementConverter = suspend (Int, String, String, Mess internal object MessageElementConverter { private val convertMap = hashMapOf( - 1 to MessageElementConverter::convertTextElem, + 1 to MessageElementConverter::convertTextElem, // MsgConstant.KELEMTYPEFACE to MessageElementConverter::convertFaceElem, // MsgConstant.KELEMTYPEPIC to MessageElementConverter::convertImageElem, // MsgConstant.KELEMTYPEPTT to MessageElementConverter::convertVoiceElem, // MsgConstant.KELEMTYPEVIDEO to MessageElementConverter::convertVideoElem, // MsgConstant.KELEMTYPEMARKETFACE to MessageElementConverter::convertMarketFaceElem, -// MsgConstant.KELEMTYPEARKSTRUCT to MessageElementConverter::convertStructJsonElem, + 51 to MessageElementConverter::convertStructJsonElem, // MsgConstant.KELEMTYPEREPLY to MessageElementConverter::convertReplyElem, // MsgConstant.KELEMTYPEGRAYTIP to MessageElementConverter::convertGrayTipsElem, // MsgConstant.KELEMTYPEFILE to MessageElementConverter::convertFileElem, @@ -328,71 +333,74 @@ internal object MessageElementConverter { // } // } // -// /** -// * JSON消息转消息段 -// */ -// private suspend fun convertStructJsonElem( -// chatType: Int, -// peerId: String, -// subPeer: String, -// element: MessageElement -// ): MessageSegment { -// val data = element.arkElement.bytesData.asJsonObject -// return when (data["app"].asString) { -// "com.tencent.multimsg" -> { -// val info = data["meta"].asJsonObject["detail"].asJsonObject -// MessageSegment( -// type = "forward", -// data = mapOf( -// "id" to info["resid"].asString -// ) -// ) -// } -// -// "com.tencent.troopsharecard" -> { -// val info = data["meta"].asJsonObject["contact"].asJsonObject -// MessageSegment( -// type = "contact", -// data = hashMapOf( -// "type" to "group", -// "id" to info["jumpUrl"].asString.split("group_code=")[1] -// ) -// ) -// } -// -// "com.tencent.contact.lua" -> { -// val info = data["meta"].asJsonObject["contact"].asJsonObject -// MessageSegment( -// type = "contact", -// data = hashMapOf( -// "type" to "private", -// "id" to info["jumpUrl"].asString.split("uin=")[1] -// ) -// ) -// } -// -// "com.tencent.map" -> { -// val info = data["meta"].asJsonObject["Location.Search"].asJsonObject -// MessageSegment( -// type = "location", -// data = hashMapOf( -// "lat" to info["lat"].asString, -// "lon" to info["lng"].asString, -// "content" to info["address"].asString, -// "title" to info["name"].asString -// ) -// ) -// } -// -// else -> MessageSegment( -// type = "json", -// data = mapOf( -// "data" to element.arkElement.bytesData.asJsonObject.toString() -// ) -// ) -// } -// } -// + /** + * JSON消息转消息段 + */ + private suspend fun convertStructJsonElem( + chatType: Int, + peerId: String, + subPeer: String, + element: MessageElement + ): MessageSegment { + val data = element.json!!.data!! + val jsonStr = + (if (data[0].toInt() == 1) DeflateTools.uncompress(data.sliceArray(1 until data.size)) else data.sliceArray(1 until data.size)).toString() + val json = jsonStr.asJsonObject + return when (json["app"].asString) { + "com.tencent.multimsg" -> { + val info = json["meta"].asJsonObject["detail"].asJsonObject + MessageSegment( + type = "forward", + data = mapOf( + "id" to info["resid"].asString + ) + ) + } + + "com.tencent.troopsharecard" -> { + val info = json["meta"].asJsonObject["contact"].asJsonObject + MessageSegment( + type = "contact", + data = hashMapOf( + "type" to "group", + "id" to info["jumpUrl"].asString.split("group_code=")[1] + ) + ) + } + + "com.tencent.contact.lua" -> { + val info = json["meta"].asJsonObject["contact"].asJsonObject + MessageSegment( + type = "contact", + data = hashMapOf( + "type" to "private", + "id" to info["jumpUrl"].asString.split("uin=")[1] + ) + ) + } + + "com.tencent.map" -> { + val info = json["meta"].asJsonObject["Location.Search"].asJsonObject + MessageSegment( + type = "location", + data = hashMapOf( + "lat" to info["lat"].asString, + "lon" to info["lng"].asString, + "content" to info["address"].asString, + "title" to info["name"].asString + ) + ) + } + + else -> MessageSegment( + type = "json", + data = mapOf( + "data" to jsonStr + ) + ) + } + } + // /** // * 回复消息转消息段 // */ diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/messageelement/MessageElementMaker.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/messageelement/MessageElementMaker.kt index 7da13f6a..c30fd7b5 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/messageelement/MessageElementMaker.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/messageelement/MessageElementMaker.kt @@ -1,14 +1,20 @@ package moe.fuqiuluo.qqinterface.servlet.msg.messageelement +import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import kotlinx.serialization.json.JsonObject +import moe.fuqiuluo.qqinterface.servlet.GProSvc +import moe.fuqiuluo.qqinterface.servlet.GroupSvc +import moe.fuqiuluo.shamrock.helper.* +import moe.fuqiuluo.shamrock.helper.Level +import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.ParamsException -import moe.fuqiuluo.shamrock.tools.asInt -import moe.fuqiuluo.shamrock.tools.asString +import moe.fuqiuluo.shamrock.tools.* import moe.fuqiuluo.shamrock.utils.DeflateTools import protobuf.message.MessageElement import protobuf.message.element.FaceElement import protobuf.message.element.JsonElement import protobuf.message.element.TextElement +import java.nio.ByteBuffer internal typealias IMessageElementMaker = suspend (Int, Long, String, JsonObject) -> Result @@ -20,7 +26,7 @@ internal object MessageElementMaker { // "image" to MessageElementMaker::createImageElem, // "voice" to MessageElementMaker::createRecordElem, // "record" to MessageElementMaker::createRecordElem, -// "at" to MessageElementMaker::createAtElem, + "at" to MessageElementMaker::createAtElem, // "video" to MessageElementMaker::createVideoElem, // "markdown" to MessageElementMaker::createMarkdownElem, // "dice" to MessageElementMaker::createDiceElem, @@ -71,6 +77,88 @@ internal object MessageElementMaker { return Result.success(elem) } + private suspend fun createAtElem( + chatType: Int, + msgId: Long, + peerId: String, + data: JsonObject + ): Result { + return if (chatType == MsgConstant.KCHATTYPEGROUP) { + data.checkAndThrow("qq") + + val qq: Long + val type: Int + lateinit var display: String + when (val qqStr = data["qq"].asString) { + "0", "all" -> { + qq = 0 + type = 1 + display = "@全体成员" + } + + "online" -> { + qq = 0 + type = 64 + display = "@在线成员" + } + + else -> { + qq = qqStr.toLong() + type = 0 + display = + "@" + (data["name"].asStringOrNull ?: GroupSvc.getTroopMemberInfoByUinV2(peerId, qqStr, true) + .onSuccess { + it.troopnick + .ifEmpty { it.friendnick } + .ifEmpty { qqStr } + }.onFailure { + LogCenter.log("无法获取群成员信息: $qqStr", Level.ERROR) + }) + } + } + + val attr6: ByteBuffer = ByteBuffer.allocate(6) + attr6.put(byteArrayOf(0, 1, 0, 0, 0)) + attr6.putChar(display.length.toChar()) + attr6.putChar(type.toChar()) + attr6.putBuf32Long(qq) + attr6.put(byteArrayOf(0, 0)) + val elem = MessageElement( + text = TextElement(text = display, attr6Buf = attr6.array()) + ) + Result.success(elem) + } else if (chatType == MsgConstant.KCHATTYPEGUILD) { + data.checkAndThrow("qq") + + val qq: Long + val type: Int + lateinit var display: String + when (val qqStr = data["qq"].asString) { + "0", "all" -> { + type = 2 + display = "@全体成员" + } + + else -> { + qq = qqStr.toLong() + type = 2 + display = + "@" + (data["name"].asStringOrNull ?: GProSvc.getUserGuildInfo(0UL, 0UL) + .onSuccess { + it.nickName.ifNullOrEmpty(qqStr) + }.onFailure { + LogCenter.log("无法获取频道组成员信息: $qqStr", Level.ERROR) + }) + } + } + + val elem = MessageElement( + text = TextElement(text = display, pbReserve = TextElement.Companion.TextResvAttr(atType = type)) + ) + Result.success(elem) + } else Result.failure(ActionMsgException) + } + private suspend fun createJsonElem( chatType: Int, msgId: Long, diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/msgelement/MsgElementConverter.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/msgelement/MsgElementConverter.kt index f61aa906..8a7ea235 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/msgelement/MsgElementConverter.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/msgelement/MsgElementConverter.kt @@ -69,7 +69,7 @@ internal object MsgElementConverter { //MsgConstant.KELEMTYPEMULTIFORWARD to MsgElementConverter::convertXmlMultiMsgElem, //MsgConstant.KELEMTYPESTRUCTLONGMSG to MsgElementConverter::convertXmlLongMsgElem, MsgConstant.KELEMTYPEFACEBUBBLE to MsgElementConverter::convertBubbleFaceElem, - MsgConstant.KELEMTYPEINLINEKEYBOARD to MsgElementConverter::convertInlineKeyboardElem, + MsgConstant.KELEMTYPEINLINEKEYBOARD to MsgElementConverter::convertInlineKeyboardElem ) operator fun get(type: Int): IMsgElementConverter? = convertMap[type] diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/msgelement/MsgElementMaker.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/msgelement/MsgElementMaker.kt index 28131b16..93bbf53f 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/msgelement/MsgElementMaker.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/msgelement/MsgElementMaker.kt @@ -755,12 +755,6 @@ internal object MsgElementMaker { at.atNtUid = "0" } - "online" -> { - at.content = "@在线成员" - at.atType = MsgConstant.ATTYPEONLINE - at.atNtUid = "0" - } - "admin" -> { at.content = "@管理员" at.atRoleId = 1 @@ -768,6 +762,12 @@ internal object MsgElementMaker { at.atNtUid = "0" } + "online" -> { + at.content = "@在线成员" + at.atType = MsgConstant.ATTYPEONLINE + at.atNtUid = "0" + } + else -> { val name = data["name"].asStringOrNull if (name == null) { diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendForwardMessage.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendForwardMessage.kt index 44769651..5d5175d8 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendForwardMessage.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendForwardMessage.kt @@ -95,11 +95,14 @@ internal object SendForwardMessage : IActionHandler() { LogCenter.log("合并转发消息节点消息(id = ${data["id"].asInt})获取失败:$it", Level.WARN) return@map null } - uid = record.peerUid if (record.chatType == MsgConstant.KCHATTYPEGROUP) groupUin = record.peerUin.toString() PushMsgBody( head = MessageHead( peerUid = record.senderUid, + receiverUid = record.peerUid, + forward = MessageForward( + friendName = record.sendNickName + ), groupInfo = if (record.chatType == MsgConstant.KCHATTYPEGROUP) GroupInfo( groupCode = record.peerUin.toULong(), memberCard = record.sendMemberName, @@ -108,22 +111,25 @@ internal object SendForwardMessage : IActionHandler() { ), content = MessageContent( msgType = when (record.chatType) { - MsgConstant.KCHATTYPEC2C -> 529 + MsgConstant.KCHATTYPEC2C -> 9 MsgConstant.KCHATTYPEGROUP -> 82 else -> throw UnsupportedOperationException( "Unsupported chatType: $chatType" ) }, + msgSubType = if (record.chatType == MsgConstant.KCHATTYPEC2C) 175 else null, + u1 = if (record.chatType == MsgConstant.KCHATTYPEC2C) 175 else null, msgViaRandom = record.msgId, - msgSeq = record.msgSeq, + msgSeq_ = record.msgSeq, // idk what this is(i++) msgTime = record.msgTime, u2 = 1, u6 = 0, u7 = 0, + msgSeq = if (record.chatType == MsgConstant.KCHATTYPEC2C) record.msgSeq else null, // seq for dm forwardHead = ForwardHead( u1 = 0, u2 = 0, - u3 = if (record.chatType == MsgConstant.KCHATTYPEGROUP) 0 else 2, + u3 = 0, ub641 = "", Avatar = "" ) @@ -164,17 +170,23 @@ internal object SendForwardMessage : IActionHandler() { PushMsgBody( head = MessageHead( peer = data["uin"]?.asLong ?: TicketSvc.getUin().toLong(), - peerUid = data["uid"]?.asString ?: TicketSvc.getUid() - + peerUid = data["uid"]?.asString ?: TicketSvc.getUid(), + receiverUid = TicketSvc.getUid(), + forward = MessageForward( + friendName = data["name"]?.asStringOrNull ?: TicketSvc.getNickname() + ) ), content = MessageContent( - msgType = 529, - msgViaRandom = 4, - msgSeq = data["seq"]?.asLong ?: Random.nextLong(), + msgType = 9, + msgSubType = 175, + u1 = 175, + msgViaRandom = Random.nextLong(), + msgSeq_ = data["seq"]?.asLong ?: Random.nextLong(), msgTime = data["time"]?.asLong ?: (System.currentTimeMillis() / 1000), u2 = 1, u6 = 0, u7 = 0, + msgSeq = data["seq"]?.asLong ?: Random.nextLong(), forwardHead = ForwardHead( u1 = 0, u2 = 0, diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/PrimitiveListener.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/PrimitiveListener.kt index 7ad79ef2..e6cb5738 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/PrimitiveListener.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/PrimitiveListener.kt @@ -111,9 +111,9 @@ internal object PrimitiveListener { private fun onGroupMessage(msgTime: Long, body: MessageBody) { runCatching { body.rich?.elements?.filter { - it.commElem != null && it.commElem!!.type == 48 + it.comm != null && it.comm!!.type == 48 }?.map { - ProtoBuf.decodeFromByteArray(it.commElem!!.data!!) + ProtoBuf.decodeFromByteArray(it.comm!!.data!!) }?.forEach { it.display?.show?.download?.url?.let { RKEY_PATTERN.matcher(it).takeIf {