diff --git a/protobuf/src/main/java/protobuf/message/MessageElement.kt b/protobuf/src/main/java/protobuf/message/MessageElement.kt index 70f8437..956dfaf 100644 --- a/protobuf/src/main/java/protobuf/message/MessageElement.kt +++ b/protobuf/src/main/java/protobuf/message/MessageElement.kt @@ -10,4 +10,4 @@ data class MessageElement( @ProtoNumber(2) val face: FaceElement? = null, @ProtoNumber(51) val json: JsonElement? = null, @ProtoNumber(53) val commElem: CommonElement? = null, -) \ No newline at end of file +) diff --git a/protobuf/src/main/java/protobuf/message/element/TextElement.kt b/protobuf/src/main/java/protobuf/message/element/TextElement.kt index 89d5e5c..0fce78b 100644 --- a/protobuf/src/main/java/protobuf/message/element/TextElement.kt +++ b/protobuf/src/main/java/protobuf/message/element/TextElement.kt @@ -6,4 +6,33 @@ import kotlinx.serialization.protobuf.ProtoNumber @Serializable data class TextElement( @ProtoNumber(1) val text: String? = null, + @ProtoNumber(2) val link: String? = null, + @ProtoNumber(3) val attr6Buf: ByteArray? = null, + @ProtoNumber(4) val attr7Buf: ByteArray? = null, + @ProtoNumber(11) val buf: ByteArray? = null, + @ProtoNumber(12) val pbReserve: TextResvAttr? = null, +) + +@Serializable +data class TextResvAttr( + @ProtoNumber(1) val wording: ByteArray? = null, + @ProtoNumber(2) val textAnalysisResult: Int? = null, + @ProtoNumber(3) val atType: Int? = null, + @ProtoNumber(4) val atMemberUin: Long? = null, + @ProtoNumber(5) val atMemberTinyid: Long? = null, + @ProtoNumber(6) val atChannelInfo: ExtChannelInfo? = null, + @ProtoNumber(7) val atRoleInfo: ExtRoleInfo? = null, +) + +@Serializable +data class ExtChannelInfo( + @ProtoNumber(1) val guildId: Long? = null, + @ProtoNumber(2) val channelId: Long? = null, +) + +@Serializable +data class ExtRoleInfo( + @ProtoNumber(1) val id: Long? = null, + @ProtoNumber(2) val info: ByteArray? = null, + @ProtoNumber(3) val flag: Int? = null, ) \ No newline at end of file diff --git a/protobuf/src/main/java/protobuf/message/longmsg/LongMsg.kt b/protobuf/src/main/java/protobuf/message/longmsg/LongMsg.kt index d1098cb..1d7c935 100644 --- a/protobuf/src/main/java/protobuf/message/longmsg/LongMsg.kt +++ b/protobuf/src/main/java/protobuf/message/longmsg/LongMsg.kt @@ -23,7 +23,7 @@ data class LongMsgUid( data class RecvLongMsgInfo( @ProtoNumber(1) val uid: LongMsgUid? = null, @ProtoNumber(2) val resId: String? = null, - @ProtoNumber(3) val acquire: Boolean? = null, + @ProtoNumber(3) val u1: Int? = null, ) @Serializable diff --git a/protobuf/src/main/java/protobuf/message/longmsg/LongMsgPayload.kt b/protobuf/src/main/java/protobuf/message/longmsg/LongMsgPayload.kt index c96cce8..328b30b 100644 --- a/protobuf/src/main/java/protobuf/message/longmsg/LongMsgPayload.kt +++ b/protobuf/src/main/java/protobuf/message/longmsg/LongMsgPayload.kt @@ -28,5 +28,5 @@ data class LongMsgAction( ) @Serializable data class LongMsgPayload( - @ProtoNumber(2) val action: LongMsgAction? = null + @ProtoNumber(2) val action: List? = null ) \ No newline at end of file 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 c304035..a6a95c5 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/MsgSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/MsgSvc.kt @@ -8,36 +8,29 @@ import com.tencent.qqnt.kernel.api.IKernelService import com.tencent.qqnt.kernel.nativeinterface.* 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.withTimeoutOrNull import kotlinx.serialization.decodeFromByteArray import kotlinx.serialization.encodeToByteArray import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonObject import kotlinx.serialization.protobuf.ProtoBuf -import moe.fuqiuluo.qqinterface.servlet.msg.convert.toSegments +import moe.fuqiuluo.qqinterface.servlet.msg.messageelement.toSegments +import moe.fuqiuluo.qqinterface.servlet.msg.toListMap import moe.fuqiuluo.shamrock.helper.ContactHelper import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.helper.MessageHelper.messageArrayToMessageElements +import moe.fuqiuluo.shamrock.remote.service.data.MessageDetail +import moe.fuqiuluo.shamrock.remote.service.data.MessageSender import moe.fuqiuluo.shamrock.remote.structures.SendMsgResult import moe.fuqiuluo.shamrock.tools.* import moe.fuqiuluo.shamrock.utils.DeflateTools import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher import moe.fuqiuluo.shamrock.xposed.helper.msgService -import protobuf.message.* import protobuf.message.longmsg.* -import tencent.mobileim.structmsg.structmsg.SystemMsg -import java.util.UUID -import kotlin.collections.slice import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine -import kotlin.random.Random -import kotlin.random.nextLong internal object MsgSvc : BaseSvc() { suspend fun prepareTempChatFromGroup( @@ -237,10 +230,12 @@ internal object MsgSvc : BaseSvc() { messages: List, ): Result { val payload = LongMsgPayload( - action = LongMsgAction( - command = "MultiMsg", - data = LongMsgContent( - messages + action = listOf( + LongMsgAction( + command = "MultiMsg", + data = LongMsgContent( + body = messages + ) ) ) ) @@ -265,49 +260,64 @@ internal object MsgSvc : BaseSvc() { ProtoBuf.encodeToByteArray(req) ) ?: return Result.failure(Exception("unable to upload multi message")) val rsp = ProtoBuf.decodeFromByteArray(buffer.slice(4)) - return rsp.sendResult?.resId?.let { Result.success(it) } ?: Result.failure(Exception("unable to upload multi message")) + return rsp.sendResult?.resId?.let { Result.success(it) } + ?: Result.failure(Exception("unable to upload multi message")) } - suspend fun getMultiMsg(resId: String): Result> { - // trpc.group.long_msg_interface.MsgService.SsoRecvLongMsg - // 00 00 00 70 0A 60 0A 1A 12 18 75 5F 35 5A 5A 53 6F 38 63 4D 71 70 49 79 63 75 57 5F 78 43 4C 48 6E 77 12 40 4D 6F 61 44 38 77 2B 55 74 43 42 55 45 4C 4F 66 7A 61 72 69 43 7A 4F 5A 44 57 4B 43 6D 68 45 74 4F 65 54 6C 46 66 44 70 2F 73 61 56 77 50 2F 44 52 37 72 4A 2B 4B 4B 47 30 65 71 2B 6C 4B 58 34 18 03 7A 08 08 02 10 02 18 09 20 00 - - 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> { - 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() + suspend fun getMultiMsg(resId: String): Result> { + val req = LongMsgReq( + recvInfo = RecvLongMsgInfo( + uid = LongMsgUid(TicketSvc.getUid()), + resId = resId, + u1 = 3 + ), + setting = LongMsgSettings( + field1 = 2, + field2 = 2, + field3 = 9, + field4 = 0 + ) + ) + val buffer = sendBufferAW( + "trpc.group.long_msg_interface.MsgService.SsoRecvLongMsg", + true, + ProtoBuf.encodeToByteArray(req) + ) ?: return Result.failure(Exception("unable to get multi message")) + val rsp = ProtoBuf.decodeFromByteArray(buffer.slice(4)) + val msg = DeflateTools.ungzip( + rsp.recvResult?.payload ?: return Result.failure(Exception("unable to get multi message")) + ) + val payload = ProtoBuf.decodeFromByteArray(msg) + payload.action?.forEach { + if (it.command == "MultiMsg") { + return Result.success(it.data?.body?.map { msg -> + val chatType = + if (msg.content!!.msgType == 1) MsgConstant.KCHATTYPEC2C else MsgConstant.KCHATTYPEGROUP + MessageDetail( + time = msg.content?.msgTime?.toInt() ?: 0, + msgType = MessageHelper.obtainDetailTypeByMsgType(chatType), + msgId = MessageHelper.generateMsgIdHash(chatType, msg.content!!.msgViaRandom), + realId = msg.content!!.msgSeq.toInt(), + sender = MessageSender( + msg.head?.peer ?: 0, + msg.head!!.groupInfo!!.memberCard ?: "", + "unknown", + 0, + msg.head!!.peerUid!!, + msg.head!!.peerUid!! + ), + message = msg.body?.rich?.elements?.toSegments(chatType, msg.head?.peer.toString(), "0") + ?.toListMap() ?: emptyList(), + peerId = msg.head?.peer ?: 0, + groupId = if (chatType == MsgConstant.KCHATTYPEGROUP) msg.head?.groupInfo?.groupCode?.toLong() + ?: 0 else 0, + targetId = if (chatType != MsgConstant.KCHATTYPEGROUP) msg.head?.peer ?: 0 else 0 + ) } + ?: return Result.failure(Exception("Msg is empty"))) } - } ?: return Result.failure(Exception("获取合并转发消息失败")) - - //msgService.deleteMsg(contact, arrayListOf(msgId), null) - - return Result.success(msgList) + } + return Result.failure(Exception("Can't find msg")) } class MessageCallback( diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/MessageSegment.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/MessageSegment.kt new file mode 100644 index 0000000..8fd3c73 --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/MessageSegment.kt @@ -0,0 +1,34 @@ +package moe.fuqiuluo.qqinterface.servlet.msg + +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import moe.fuqiuluo.shamrock.tools.json + + +internal data class MessageSegment( + val type: String, + val data: Map = emptyMap() +) { + fun toJson(): JsonObject { + return hashMapOf( + "type" to type.json, + "data" to data.json + ).json + } +} + +internal fun List.toJson(): JsonArray { + return this.map { + it.toJson() + }.json +} + +internal fun List.toListMap(): List> { + return this.map { + hashMapOf( + "type" to it.type.json, + "data" to it.data.json + ).json + } +} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/convert/MessageConvert.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/convert/MessageConvert.kt deleted file mode 100644 index 3c15ebe..0000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/convert/MessageConvert.kt +++ /dev/null @@ -1,133 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet.msg.convert - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import com.tencent.qqnt.kernel.nativeinterface.MsgElement -import com.tencent.qqnt.kernel.nativeinterface.MsgRecord -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.msg.convert.MessageElemConverter.* -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.tools.json - -internal typealias MessageSegmentList = ArrayList - -internal data class MessageSegment( - val type: String, - val data: Map = emptyMap() -) { - fun toJson(): Map { - return hashMapOf( - "type" to type.json, - "data" to data.json - ) - } -} - -internal suspend fun MsgRecord.toSegments(): ArrayList { - return MessageConvert.convertMessageRecordToMsgSegment(this) -} - -internal suspend fun MsgRecord.toCQCode(): String { - return MessageConvert.convertMessageRecordToCQCode(this) -} - -internal suspend fun List.toSegments(chatType: Int, peerId: String, subPeer: String): MessageSegmentList { - return MessageConvert.convertMessageElementsToMsgSegment(chatType, this, peerId, subPeer) -} - -internal suspend fun List.toCQCode(chatType: Int, peerId: String, subPeer: String): String { - return MessageConvert.convertMsgElementsToCQCode(this, chatType, peerId, subPeer) -} - - -internal object MessageConvert { - private val convertMap by lazy { - mutableMapOf( - MsgConstant.KELEMTYPETEXT to TextConverter, - MsgConstant.KELEMTYPEFACE to FaceConverter, - MsgConstant.KELEMTYPEPIC to ImageConverter, - MsgConstant.KELEMTYPEPTT to VoiceConverter, - MsgConstant.KELEMTYPEVIDEO to VideoConverter, - MsgConstant.KELEMTYPEMARKETFACE to MarketFaceConverter, - MsgConstant.KELEMTYPEARKSTRUCT to StructJsonConverter, - MsgConstant.KELEMTYPEREPLY to ReplyConverter, - MsgConstant.KELEMTYPEGRAYTIP to GrayTipsConverter, - MsgConstant.KELEMTYPEFILE to FileConverter, - MsgConstant.KELEMTYPEMARKDOWN to MarkdownConverter, - //MsgConstant.KELEMTYPEMULTIFORWARD to XmlMultiMsgConverter, - //MsgConstant.KELEMTYPESTRUCTLONGMSG to XmlLongMsgConverter, - MsgConstant.KELEMTYPEFACEBUBBLE to BubbleFaceConverter, - MsgConstant.KELEMTYPEINLINEKEYBOARD to InlineKeyboardConverter, - ) - } - - suspend fun convertMessageElementsToMsgSegment( - chatType: Int, - elements: List, - peerId: String, - subPeer: String - ): ArrayList { - val messageData = arrayListOf() - elements.forEach { msg -> - kotlin.runCatching { - val elementId = msg.elementType - val converter = convertMap[elementId] - converter?.convert(chatType, peerId, subPeer, msg) - ?: throw UnsupportedOperationException("不支持的消息element类型:$elementId") - }.onSuccess { - messageData.add(it) - }.onFailure { - if (it is UnknownError) { - // 不处理的消息类型,抛出unknown error - } else { - LogCenter.log("消息element转换错误:$it, elementType: ${msg.elementType}", Level.WARN) - } - } - } - return messageData - } - - suspend fun convertMessageRecordToMsgSegment(record: MsgRecord, chatType: Int = record.chatType): ArrayList { - val peerId = when(chatType) { - MsgConstant.KCHATTYPEGUILD -> record.guildId - else -> record.peerUin.toString() - } - return convertMessageElementsToMsgSegment(chatType, record.elements, peerId, record.channelId ?: peerId) - } - - suspend fun convertMsgElementsToCQCode( - elements: List, - chatType: Int, - peerId: String, - subPeer: String - ): String { - if(elements.isEmpty()) { - return "" - } - val msgList = convertMessageElementsToMsgSegment(chatType, elements, peerId, subPeer).map { - it.toJson() - } - return MessageHelper.encodeCQCode(msgList) - } - - suspend fun convertMessageRecordToCQCode(record: MsgRecord, chatType: Int = record.chatType): String { - val peerId = when(chatType) { - MsgConstant.KCHATTYPEGUILD -> record.guildId - else -> record.peerUin.toString() - } - return MessageHelper.encodeCQCode( - convertMessageElementsToMsgSegment( - chatType, - record.elements, - peerId, - record.channelId ?: peerId - ).map { it.toJson() } - ) - } -} - -internal fun interface IMessageConvert { - suspend fun convert(chatType: Int, peerId: String, subPeer: String, element: MsgElement): MessageSegment -} - diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/convert/MessageElemConverter.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/convert/MessageElemConverter.kt deleted file mode 100644 index 7175abf..0000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/convert/MessageElemConverter.kt +++ /dev/null @@ -1,558 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet.msg.convert - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import com.tencent.qqnt.kernel.nativeinterface.MsgElement -import kotlinx.serialization.json.add -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.put -import kotlinx.serialization.json.putJsonArray -import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc -import moe.fuqiuluo.shamrock.helper.ContactHelper -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.helper.db.ImageDB -import moe.fuqiuluo.shamrock.helper.db.ImageMapping -import moe.fuqiuluo.shamrock.helper.db.MessageDB -import moe.fuqiuluo.shamrock.tools.asJsonObject -import moe.fuqiuluo.shamrock.tools.asString -import moe.fuqiuluo.shamrock.tools.hex2ByteArray -import moe.fuqiuluo.shamrock.tools.json - -internal sealed class MessageElemConverter: IMessageConvert { - /** - * 文本 / 艾特 消息转换消息段 - */ - data object TextConverter: MessageElemConverter() { - override suspend fun convert( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val text = element.textElement - return if (text.atType != MsgConstant.ATTYPEUNKNOWN) { - MessageSegment( - type = "at", - data = hashMapOf( - "qq" to ContactHelper.getUinByUidAsync(text.atNtUid), - ) - ) - } else { - MessageSegment( - type = "text", - data = hashMapOf( - "text" to text.content - ) - ) - } - } - } - - /** - * 小表情 / 戳一戳 消息转换消息段 - */ - data object FaceConverter: MessageElemConverter() { - override suspend fun convert( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val face = element.faceElement - - if (face.faceType == 5) { - return MessageSegment( - type = "poke", - data = hashMapOf( - "type" to face.pokeType, - "id" to face.vaspokeId, - "strength" to face.pokeStrength - ) - ) - } - when (face.faceIndex) { - 114 -> { - return MessageSegment( - type = "basketball", - data = hashMapOf( - "id" to face.resultId.ifEmpty { "0" }.toInt(), - ) - ) - } - 358 -> { - if (face.sourceType == 1) return MessageSegment("new_dice") - return MessageSegment( - type = "new_dice", - data = hashMapOf( - "id" to face.resultId.ifEmpty { "0" }.toInt() - ) - ) - } - 359 -> { - if (face.resultId.isEmpty()) return MessageSegment("new_rps") - return MessageSegment( - type = "new_rps", - data = hashMapOf( - "id" to face.resultId.ifEmpty { "0" }.toInt() - ) - ) - } - 394 -> { - //LogCenter.log(face.toString()) - return MessageSegment( - type = "face", - data = hashMapOf( - "id" to face.faceIndex, - "big" to (face.faceType == 3), - "result" to (face.resultId ?: "1") - ) - ) - } - else -> return MessageSegment( - type = "face", - data = hashMapOf( - "id" to face.faceIndex, - "big" to (face.faceType == 3) - ) - ) - } - } - } - - /** - * 图片消息转换消息段 - */ - data object ImageConverter: MessageElemConverter() { - override suspend fun convert( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val image = element.picElement - val md5 = image.md5HexStr ?: image.fileName - .replace("{", "") - .replace("}", "") - .replace("-", "").split(".")[0] - - ImageDB.getInstance().imageMappingDao().insert( - ImageMapping(md5.uppercase(), chatType, image.fileSize) - ) - - //LogCenter.log(image.toString()) - - val originalUrl = image.originImageUrl ?: "" - //LogCenter.log({ "receive image: $image" }, Level.DEBUG) - - return MessageSegment( - type = "image", - data = hashMapOf( - "file" to md5, - "url" to when(chatType) { - MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(originalUrl, md5) - MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(originalUrl, md5) - MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(originalUrl, md5) - else -> unknownChatType(chatType) - }, - "subType" to image.picSubType, - "type" to if (image.isFlashPic == true) "flash" else if(image.original) "original" else "show" - ) - ) - } - } - - /** - * 语音消息转换消息段 - */ - data object VoiceConverter: MessageElemConverter() { - override suspend fun convert( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val record = element.pttElement - - val md5 = if (record.fileName.startsWith("silk")) - record.fileName.substring(5) - else record.md5HexStr - - return MessageSegment( - type = "record", - data = hashMapOf( - "file" to md5, - "url" to when(chatType) { - MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPttDownUrl("0", record.md5HexStr, record.fileUuid) - MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPttDownUrl("0", record.fileUuid) - MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupPttDownUrl("0", record.md5HexStr, record.fileUuid) - else -> unknownChatType(chatType) - } - ).also { - if(record.voiceChangeType != MsgConstant.KPTTVOICECHANGETYPENONE) { - it["magic"] = "1".json - } - if ((it["url"] as String).isBlank()) { - it.remove("url") - } - } - ) - } - } - - /** - * 视频消息转换消息段 - */ - data object VideoConverter: MessageElemConverter() { - override suspend fun convert( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val video = element.videoElement - val md5 = if (video.fileName.contains("/")) { - video.videoMd5.takeIf { - !it.isNullOrEmpty() - }?.hex2ByteArray() ?: video.fileName.split("/").let { - it[it.size - 2].hex2ByteArray() - } - } else video.fileName.split(".")[0].hex2ByteArray() - - //LogCenter.log({ "receive video msg: $video" }, Level.DEBUG) - - return MessageSegment( - type = "video", - data = hashMapOf( - "file" to video.fileName, - "url" to when(chatType) { - MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid) - MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", md5, video.fileUuid) - MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid) - else -> unknownChatType(chatType) - } - ).also { - if ((it["url"] as String).isBlank()) - it.remove("url") - } - ) - } - } - - /** - * 商城大表情消息转换消息段 - */ - data object MarketFaceConverter: MessageElemConverter() { - override suspend fun convert( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val face = element.marketFaceElement - return when (face.emojiId.lowercase()) { - "4823d3adb15df08014ce5d6796b76ee1" -> MessageSegment("dice") - "83c8a293ae65ca140f348120a77448ee" -> MessageSegment("rps") - else -> MessageSegment( - type = "mface", - data = hashMapOf( - "id" to face.emojiId - ) - ) - } - } - } - - /** - * JSON消息转消息段 - */ - data object StructJsonConverter: MessageElemConverter() { - override suspend fun convert( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): 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() - ) - ) - } - } - } - - /** - * 回复消息转消息段 - */ - data object ReplyConverter: MessageElemConverter() { - override suspend fun convert( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val reply = element.replyElement - val msgId = reply.replayMsgId - val msgHash = if (msgId != 0L) { - MessageHelper.generateMsgIdHash(chatType, msgId) - } else { - MessageDB.getInstance().messageMappingDao() - .queryByMsgSeq(chatType, peerId, reply.replayMsgSeq?.toInt() ?: 0)?.msgHashId - ?: - kotlin.run { - LogCenter.log("消息映射关系未找到: Message($reply)", Level.WARN) - MessageHelper.generateMsgIdHash(chatType, reply.sourceMsgIdInRecords) - } - } - - return MessageSegment( - type = "reply", - data = mapOf( - "id" to msgHash - ) - ) - } - } - - /** - * 灰色提示条消息过滤 - */ - data object GrayTipsConverter: MessageElemConverter() { - override suspend fun convert( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val tip = element.grayTipElement - when(tip.subElementType) { - MsgConstant.GRAYTIPELEMENTSUBTYPEJSON -> { - val notify = tip.jsonGrayTipElement - when(notify.busiId) { - /* 新人入群 */ 17L, /* 群戳一戳 */1061L, - /* 群撤回 */1014L, /* 群设精消息 */2401L, - /* 群头衔 */2407L -> {} - else -> LogCenter.log("不支持的灰条类型(JSON): ${notify.busiId}", Level.WARN) - } - } - MsgConstant.GRAYTIPELEMENTSUBTYPEXMLMSG -> { - val notify = tip.xmlElement - when(notify.busiId) { - /* 群戳一戳 */1061L, /* 群打卡 */1068L -> {} - else -> LogCenter.log("不支持的灰条类型(XML): ${notify.busiId}", Level.WARN) - } - } - else -> LogCenter.log("不支持的提示类型: ${tip.subElementType}", Level.WARN) - } - // 提示类消息,这里提供的是一个xml,不具备解析通用性 - // 在这里不推送 - throw UnknownError() - } - } - - /** - * 文件消息转换消息段 - */ - data object FileConverter: MessageElemConverter() { - override suspend fun convert( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val fileMsg = element.fileElement - val fileName = fileMsg.fileName - val fileSize = fileMsg.fileSize - val expireTime = fileMsg.expireTime ?: 0 - val fileId = fileMsg.fileUuid - val bizId = fileMsg.fileBizId ?: 0 - val fileSubId = fileMsg.fileSubId ?: "" - val url = when (chatType) { - MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId) - MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildFileDownUrl(peerId, subPeer, fileId, bizId) - else -> RichProtoSvc.getGroupFileDownUrl(peerId.toLong(), fileId, bizId) - } - - return MessageSegment( - type = "file", - data = mapOf( - "name" to fileName, - "size" to fileSize, - "expire" to expireTime, - "id" to fileId, - "url" to url, - "biz" to bizId, - "sub" to fileSubId - ) - ) - } - } - - /** - * 老板QQ的合并转发信息 - */ - data object XmlMultiMsgConverter: MessageElemConverter() { - override suspend fun convert( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val multiMsg = element.multiForwardMsgElement - return MessageSegment( - type = "forward", - data = mapOf( - "id" to multiMsg.resId - ) - ) - } - } - - data object XmlLongMsgConverter: MessageElemConverter() { - override suspend fun convert( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val longMsg = element.structLongMsgElement - return MessageSegment( - type = "forward", - data = mapOf( - "id" to longMsg.resId - ) - ) - } - } - - data object MarkdownConverter: MessageElemConverter() { - override suspend fun convert( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val markdown = element.markdownElement - return MessageSegment( - type = "markdown", - data = mapOf( - "content" to markdown.content - ) - ) - } - } - - data object BubbleFaceConverter: MessageElemConverter() { - override suspend fun convert( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val bubbleElement = element.faceBubbleElement - return MessageSegment( - type = "bubble_face", - data = mapOf( - "id" to bubbleElement.yellowFaceInfo.index, - "count" to (bubbleElement.faceCount ?: 1), - ) - ) - } - } - - data object InlineKeyboardConverter: MessageElemConverter() { - override suspend fun convert( - chatType: Int, - peerId: String, - subPeer: String, - element: MsgElement - ): MessageSegment { - val keyboard = element.inlineKeyboardElement - return MessageSegment( - type = "inline_keyboard", - data = mapOf( - "data" to buildJsonObject { - putJsonArray("rows") { - keyboard.rows.forEach { row -> - add(buildJsonObject row@{ - putJsonArray("buttons") { - row.buttons.forEach { button -> - add(buildJsonObject { - put("id", button.id ?: "") - put("label", button.label ?: "") - put("visited_label", button.visitedLabel ?: "") - put("style", button.style) - put("type", button.type) - put("click_limit", button.clickLimit) - put("unsupport_tips", button.unsupportTips ?: "") - put("data", button.data) - put("at_bot_show_channel_list", button.atBotShowChannelList) - put("permission_type", button.permissionType) - putJsonArray("specify_role_ids") { - button.specifyRoleIds?.forEach { add(it) } - } - putJsonArray("specify_tinyids") { - button.specifyTinyids?.forEach { add(it) } - } - }) - } - } - }) - } - } - put("bot_appid", keyboard.botAppid) - }.toString() - ) - ) - } - } - - protected fun unknownChatType(chatType: Int) { - throw UnsupportedOperationException("Not supported chat type: $chatType") - } -} 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 new file mode 100644 index 0000000..4583ce4 --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/messageelement/MessageElementConverter.kt @@ -0,0 +1,608 @@ +package moe.fuqiuluo.qqinterface.servlet.msg.messageelement + +import kotlinx.io.core.ByteReadPacket +import kotlinx.io.core.discardExact +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 protobuf.message.MessageElement + + +internal suspend fun List.toSegments( + chatType: Int, + peerId: String, + subPeer: String +): List { + val messageData = arrayListOf() + this.forEach { msg -> + kotlin.runCatching { + val elementType = if (msg.text != null) { + 1 + } else if (msg.face != null) { + 2 + } else if (msg.json != null) { + 51 + } else + throw UnsupportedOperationException("不支持的消息element类型:$msg") + val converter = MessageElementConverter[elementType] + converter?.invoke(chatType, peerId, subPeer, msg) + ?: throw UnsupportedOperationException("不支持的消息element类型:$elementType") + }.onSuccess { + messageData.add(it) + }.onFailure { + if (it is UnknownError) { + // 不处理的消息类型,抛出unknown error + } else { + LogCenter.log("消息element转换错误:$it", Level.WARN) + } + } + } + return messageData +} + +internal typealias IMessageElementConverter = suspend (Int, String, String, MessageElement) -> MessageSegment + +internal object MessageElementConverter { + private val convertMap = hashMapOf( + 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, +// MsgConstant.KELEMTYPEREPLY to MessageElementConverter::convertReplyElem, +// MsgConstant.KELEMTYPEGRAYTIP to MessageElementConverter::convertGrayTipsElem, +// MsgConstant.KELEMTYPEFILE to MessageElementConverter::convertFileElem, +// MsgConstant.KELEMTYPEMARKDOWN to MessageElementConverter::convertMarkdownElem, +// //MsgConstant.KELEMTYPEMULTIFORWARD to MessageElementConverter::convertXmlMultiMsgElem, +// //MsgConstant.KELEMTYPESTRUCTLONGMSG to MessageElementConverter::convertXmlLongMsgElem, +// MsgConstant.KELEMTYPEFACEBUBBLE to MessageElementConverter::convertBubbleFaceElem, +// MsgConstant.KELEMTYPEINLINEKEYBOARD to MessageElementConverter::convertInlineKeyboardElem, + ) + + operator fun get(type: Int): IMessageElementConverter? = convertMap[type] + + /** + * 文本 / 艾特 消息转换消息段 + */ + private suspend fun convertTextElem( + chatType: Int, + peerId: String, + subPeer: String, + element: MessageElement + ): MessageSegment { + val text = element.text!! + if (text.attr6Buf != null) { + val at = ByteReadPacket(text.attr6Buf!!) + at.discardExact(7) + val uin = at.readUInt() + return MessageSegment( + type = "at", + data = hashMapOf( + "qq" to uin + ) + ) + } else if (text.pbReserve != null) { + val resv = text.pbReserve!! + return MessageSegment( + type = "at", + data = hashMapOf( + "qq" to when (resv.atType) { + 2 -> resv.atMemberTinyid!! + 4 -> resv.atChannelInfo!!.channelId!! + else -> throw UnsupportedOperationException("Unknown at type: ${resv.atType}") + } + ) + ) + } else { + return MessageSegment( + type = "text", + data = hashMapOf( + "text" to text.text!! + ) + ) + } + } + +// /** +// * 小表情 / 戳一戳 消息转换消息段 +// */ +// private suspend fun convertFaceElem( +// chatType: Int, +// peerId: String, +// subPeer: String, +// element: MessageElement +// ): MessageSegment { +// val face = element.faceElement +// +// if (face.faceType == 5) { +// return MessageSegment( +// type = "poke", +// data = hashMapOf( +// "type" to face.pokeType, +// "id" to face.vaspokeId, +// "strength" to face.pokeStrength +// ) +// ) +// } +// when (face.faceIndex) { +// 114 -> { +// return MessageSegment( +// type = "basketball", +// data = hashMapOf( +// "id" to face.resultId.ifEmpty { "0" }.toInt(), +// ) +// ) +// } +// +// 358 -> { +// if (face.sourceType == 1) return MessageSegment("new_dice") +// return MessageSegment( +// type = "new_dice", +// data = hashMapOf( +// "id" to face.resultId.ifEmpty { "0" }.toInt() +// ) +// ) +// } +// +// 359 -> { +// if (face.resultId.isEmpty()) return MessageSegment("new_rps") +// return MessageSegment( +// type = "new_rps", +// data = hashMapOf( +// "id" to face.resultId.ifEmpty { "0" }.toInt() +// ) +// ) +// } +// +// 394 -> { +// //LogCenter.log(face.toString()) +// return MessageSegment( +// type = "face", +// data = hashMapOf( +// "id" to face.faceIndex, +// "big" to (face.faceType == 3), +// "result" to (face.resultId ?: "1") +// ) +// ) +// } +// +// else -> return MessageSegment( +// type = "face", +// data = hashMapOf( +// "id" to face.faceIndex, +// "big" to (face.faceType == 3) +// ) +// ) +// } +// } +// +// /** +// * 图片消息转换消息段 +// */ +// private suspend fun convertImageElem( +// chatType: Int, +// peerId: String, +// subPeer: String, +// element: MessageElement +// ): MessageSegment { +// val image = element.picElement +// val md5 = image.md5HexStr ?: image.fileName +// .replace("{", "") +// .replace("}", "") +// .replace("-", "").split(".")[0] +// +// ImageDB.getInstance().imageMappingDao().insert( +// ImageMapping(md5.uppercase(), chatType, image.fileSize) +// ) +// +// //LogCenter.log(image.toString()) +// +// val originalUrl = image.originImageUrl ?: "" +// //LogCenter.log({ "receive image: $image" }, Level.DEBUG) +// +// return MessageSegment( +// type = "image", +// data = hashMapOf( +// "file" to md5, +// "url" to when (chatType) { +// MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl( +// originalUrl, +// md5 +// ) +// +// MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(originalUrl, md5) +// MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(originalUrl, md5) +// else -> throw UnsupportedOperationException("Not supported chat type: $chatType") +// }, +// "subType" to image.picSubType, +// "type" to if (image.isFlashPic == true) "flash" else if (image.original) "original" else "show" +// ) +// ) +// } +// +// /** +// * 语音消息转换消息段 +// */ +// private suspend fun convertVoiceElem( +// chatType: Int, +// peerId: String, +// subPeer: String, +// element: MessageElement +// ): MessageSegment { +// val record = element.pttElement +// +// val md5 = if (record.fileName.startsWith("silk")) +// record.fileName.substring(5) +// else record.md5HexStr +// +// return MessageSegment( +// type = "record", +// data = hashMapOf( +// "file" to md5, +// "url" to when (chatType) { +// MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPttDownUrl( +// "0", +// record.md5HexStr, +// record.fileUuid +// ) +// +// MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPttDownUrl("0", record.fileUuid) +// MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupPttDownUrl( +// "0", +// record.md5HexStr, +// record.fileUuid +// ) +// +// else -> throw UnsupportedOperationException("Not supported chat type: $chatType") +// } +// ).also { +// if (record.voiceChangeType != MsgConstant.KPTTVOICECHANGETYPENONE) { +// it["magic"] = "1" +// } +// if ((it["url"] as String).isBlank()) { +// it.remove("url") +// } +// } +// ) +// } +// +// /** +// * 视频消息转换消息段 +// */ +// private suspend fun convertVideoElem( +// chatType: Int, +// peerId: String, +// subPeer: String, +// element: MessageElement +// ): MessageSegment { +// val video = element.videoElement +// val md5 = if (video.fileName.contains("/")) { +// video.videoMd5.takeIf { +// !it.isNullOrEmpty() +// }?.hex2ByteArray() ?: video.fileName.split("/").let { +// it[it.size - 2].hex2ByteArray() +// } +// } else video.fileName.split(".")[0].hex2ByteArray() +// +// //LogCenter.log({ "receive video msg: $video" }, Level.DEBUG) +// +// return MessageSegment( +// type = "video", +// data = hashMapOf( +// "file" to video.fileName, +// "url" to when (chatType) { +// MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid) +// MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", md5, video.fileUuid) +// MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid) +// else -> throw UnsupportedOperationException("Not supported chat type: $chatType") +// } +// ).also { +// if ((it["url"] as String).isBlank()) +// it.remove("url") +// } +// ) +// } +// +// /** +// * 商城大表情消息转换消息段 +// */ +// private suspend fun convertMarketFaceElem( +// chatType: Int, +// peerId: String, +// subPeer: String, +// element: MessageElement +// ): MessageSegment { +// val face = element.marketFaceElement +// return when (face.emojiId.lowercase()) { +// "4823d3adb15df08014ce5d6796b76ee1" -> MessageSegment("dice") +// "83c8a293ae65ca140f348120a77448ee" -> MessageSegment("rps") +// else -> MessageSegment( +// type = "mface", +// data = hashMapOf( +// "id" to face.emojiId +// ) +// ) +// } +// } +// +// /** +// * 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() +// ) +// ) +// } +// } +// +// /** +// * 回复消息转消息段 +// */ +// private suspend fun convertReplyElem( +// chatType: Int, +// peerId: String, +// subPeer: String, +// element: MessageElement +// ): MessageSegment { +// val reply = element.replyElement +// val msgId = reply.replayMsgId +// val msgHash = if (msgId != 0L) { +// MessageHelper.generateMsgIdHash(chatType, msgId) +// } else { +// MessageDB.getInstance().messageMappingDao() +// .queryByMsgSeq(chatType, peerId, reply.replayMsgSeq?.toInt() ?: 0)?.msgHashId +// ?: kotlin.run { +// LogCenter.log("消息映射关系未找到: Message($reply)", Level.WARN) +// MessageHelper.generateMsgIdHash(chatType, reply.sourceMsgIdInRecords) +// } +// } +// +// return MessageSegment( +// type = "reply", +// data = mapOf( +// "id" to msgHash +// ) +// ) +// } +// +// /** +// * 灰色提示条消息过滤 +// */ +// private suspend fun convertGrayTipsElem( +// chatType: Int, +// peerId: String, +// subPeer: String, +// element: MessageElement +// ): MessageSegment { +// val tip = element.grayTipElement +// when (tip.subElementType) { +// MsgConstant.GRAYTIPELEMENTSUBTYPEJSON -> { +// val notify = tip.jsonGrayTipElement +// when (notify.busiId) { +// /* 新人入群 */ 17L, /* 群戳一戳 */1061L, +// /* 群撤回 */1014L, /* 群设精消息 */2401L, +// /* 群头衔 */2407L -> { +// } +// +// else -> LogCenter.log("不支持的灰条类型(JSON): ${notify.busiId}", Level.WARN) +// } +// } +// +// MsgConstant.GRAYTIPELEMENTSUBTYPEXMLMSG -> { +// val notify = tip.xmlElement +// when (notify.busiId) { +// /* 群戳一戳 */1061L, /* 群打卡 */1068L -> {} +// else -> LogCenter.log("不支持的灰条类型(XML): ${notify.busiId}", Level.WARN) +// } +// } +// +// else -> LogCenter.log("不支持的提示类型: ${tip.subElementType}", Level.WARN) +// } +// // 提示类消息,这里提供的是一个xml,不具备解析通用性 +// // 在这里不推送 +// throw UnknownError() +// } +// +// /** +// * 文件消息转换消息段 +// */ +// private suspend fun convertFileElem( +// chatType: Int, +// peerId: String, +// subPeer: String, +// element: MessageElement +// ): MessageSegment { +// val fileMsg = element.fileElement +// val fileName = fileMsg.fileName +// val fileSize = fileMsg.fileSize +// val expireTime = fileMsg.expireTime ?: 0 +// val fileId = fileMsg.fileUuid +// val bizId = fileMsg.fileBizId ?: 0 +// val fileSubId = fileMsg.fileSubId ?: "" +// val url = when (chatType) { +// MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId) +// MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildFileDownUrl(peerId, subPeer, fileId, bizId) +// else -> RichProtoSvc.getGroupFileDownUrl(peerId.toLong(), fileId, bizId) +// } +// +// return MessageSegment( +// type = "file", +// data = mapOf( +// "name" to fileName, +// "size" to fileSize, +// "expire" to expireTime, +// "id" to fileId, +// "url" to url, +// "biz" to bizId, +// "sub" to fileSubId +// ) +// ) +// } +// +// /** +// * 老板QQ的合并转发信息 +// */ +// private suspend fun convertXmlMultiMsgElem( +// chatType: Int, +// peerId: String, +// subPeer: String, +// element: MessageElement +// ): MessageSegment { +// val multiMsg = element.multiForwardMessageElement +// return MessageSegment( +// type = "forward", +// data = mapOf( +// "id" to multiMsg.resId +// ) +// ) +// } +// +// private suspend fun convertXmlLongMsgElem( +// chatType: Int, +// peerId: String, +// subPeer: String, +// element: MessageElement +// ): MessageSegment { +// val longMsg = element.structLongMessageElement +// return MessageSegment( +// type = "forward", +// data = mapOf( +// "id" to longMsg.resId +// ) +// ) +// } +// +// private suspend fun convertMarkdownElem( +// chatType: Int, +// peerId: String, +// subPeer: String, +// element: MessageElement +// ): MessageSegment { +// val markdown = element.markdownElement +// return MessageSegment( +// type = "markdown", +// data = mapOf( +// "content" to markdown.content +// ) +// ) +// } +// +// private suspend fun convertBubbleFaceElem( +// chatType: Int, +// peerId: String, +// subPeer: String, +// element: MessageElement +// ): MessageSegment { +// val bubbleElement = element.faceBubbleElement +// return MessageSegment( +// type = "bubble_face", +// data = mapOf( +// "id" to bubbleElement.yellowFaceInfo.index, +// "count" to (bubbleElement.faceCount ?: 1), +// ) +// ) +// } +// +// private suspend fun convertInlineKeyboardElem( +// chatType: Int, +// peerId: String, +// subPeer: String, +// element: MessageElement +// ): MessageSegment { +// val keyboard = element.inlineKeyboardElement +// return MessageSegment( +// type = "inline_keyboard", +// data = mapOf( +// "data" to buildJsonObject { +// putJsonArray("rows") { +// keyboard.rows.forEach { row -> +// add(buildJsonObject row@{ +// putJsonArray("buttons") { +// row.buttons.forEach { button -> +// add(buildJsonObject { +// put("id", button.id ?: "") +// put("label", button.label ?: "") +// put("visited_label", button.visitedLabel ?: "") +// put("style", button.style) +// put("type", button.type) +// put("click_limit", button.clickLimit) +// put("unsupport_tips", button.unsupportTips ?: "") +// put("data", button.data) +// put("at_bot_show_channel_list", button.atBotShowChannelList) +// put("permission_type", button.permissionType) +// putJsonArray("specify_role_ids") { +// button.specifyRoleIds?.forEach { add(it) } +// } +// putJsonArray("specify_tinyids") { +// button.specifyTinyids?.forEach { add(it) } +// } +// }) +// } +// } +// }) +// } +// } +// put("bot_appid", keyboard.botAppid) +// }.toString() +// ) +// ) +// } +} diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/MessageElementMaker.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/messageelement/MessageElementMaker.kt similarity index 91% rename from xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/MessageElementMaker.kt rename to xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/messageelement/MessageElementMaker.kt index 45c00c9..7da13f6 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/MessageElementMaker.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/messageelement/MessageElementMaker.kt @@ -1,4 +1,4 @@ -package moe.fuqiuluo.qqinterface.servlet.msg +package moe.fuqiuluo.qqinterface.servlet.msg.messageelement import kotlinx.serialization.json.JsonObject import moe.fuqiuluo.shamrock.helper.ParamsException @@ -7,10 +7,10 @@ import moe.fuqiuluo.shamrock.tools.asString 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 - -internal typealias IMessageMaker = suspend (Int, Long, String, JsonObject) -> Result +internal typealias IMessageElementMaker = suspend (Int, Long, String, JsonObject) -> Result internal object MessageElementMaker { private val makerArray = hashMapOf( @@ -43,6 +43,8 @@ internal object MessageElementMaker { //"bubble_face" to MessageElementMaker::createBubbleFaceElem, ) + operator fun get(type: String): IMessageElementMaker? = makerArray[type] + private suspend fun createTextElem( chatType: Int, msgId: Long, @@ -78,7 +80,7 @@ internal object MessageElementMaker { data.checkAndThrow("data") val elem = MessageElement( - json = protobuf.message.element.JsonElement( + json = JsonElement( data = DeflateTools.compress(data.toString().toByteArray()) ) ) @@ -90,7 +92,4 @@ internal object MessageElementMaker { if (!containsKey(it)) throw ParamsException(it) } } - - operator fun get(type: String): IMessageMaker? = makerArray[type] - -} +} \ No newline at end of file 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 new file mode 100644 index 0000000..f61aa90 --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/msgelement/MsgElementConverter.kt @@ -0,0 +1,603 @@ +package moe.fuqiuluo.qqinterface.servlet.msg.msgelement + +import com.tencent.qqnt.kernel.nativeinterface.MsgConstant +import com.tencent.qqnt.kernel.nativeinterface.MsgElement +import kotlinx.serialization.json.* +import moe.fuqiuluo.qqinterface.servlet.msg.MessageSegment +import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc +import moe.fuqiuluo.shamrock.helper.ContactHelper +import moe.fuqiuluo.shamrock.helper.Level +import moe.fuqiuluo.shamrock.helper.LogCenter +import moe.fuqiuluo.shamrock.helper.MessageHelper +import moe.fuqiuluo.shamrock.helper.db.ImageDB +import moe.fuqiuluo.shamrock.helper.db.ImageMapping +import moe.fuqiuluo.shamrock.helper.db.MessageDB +import moe.fuqiuluo.shamrock.tools.asJsonObject +import moe.fuqiuluo.shamrock.tools.asString +import moe.fuqiuluo.shamrock.tools.hex2ByteArray + + +internal suspend fun List.toSegments(chatType: Int, peerId: String, subPeer: String): List { + val messageData = arrayListOf() + this.forEach { msg -> + kotlin.runCatching { + val converter = MsgElementConverter[msg.elementType] + converter?.invoke(chatType, peerId, subPeer, msg) + ?: throw UnsupportedOperationException("不支持的消息element类型:${msg.elementType}") + }.onSuccess { + messageData.add(it) + }.onFailure { + if (it is UnknownError) { + // 不处理的消息类型,抛出unknown error + } else { + LogCenter.log("消息element转换错误:$it, elementType: ${msg.elementType}", Level.WARN) + } + } + } + return messageData +} + +internal suspend fun List.toCQCode(chatType: Int, peerId: String, subPeer: String): String { + if (this.isEmpty()) { + return "" + } + return MessageHelper.nativeEncodeCQCode(this.toSegments(chatType, peerId, subPeer).map { + val params = hashMapOf() + params["_type"] = it.type + it.data.forEach { (key, value) -> + params[key] = value.toString() + } + params + }) +} + +internal typealias IMsgElementConverter = suspend (Int, String, String, MsgElement) -> MessageSegment + +internal object MsgElementConverter { + private val convertMap = hashMapOf( + MsgConstant.KELEMTYPETEXT to MsgElementConverter::convertTextElem, + MsgConstant.KELEMTYPEFACE to MsgElementConverter::convertFaceElem, + MsgConstant.KELEMTYPEPIC to MsgElementConverter::convertImageElem, + MsgConstant.KELEMTYPEPTT to MsgElementConverter::convertVoiceElem, + MsgConstant.KELEMTYPEVIDEO to MsgElementConverter::convertVideoElem, + MsgConstant.KELEMTYPEMARKETFACE to MsgElementConverter::convertMarketFaceElem, + MsgConstant.KELEMTYPEARKSTRUCT to MsgElementConverter::convertStructJsonElem, + MsgConstant.KELEMTYPEREPLY to MsgElementConverter::convertReplyElem, + MsgConstant.KELEMTYPEGRAYTIP to MsgElementConverter::convertGrayTipsElem, + MsgConstant.KELEMTYPEFILE to MsgElementConverter::convertFileElem, + MsgConstant.KELEMTYPEMARKDOWN to MsgElementConverter::convertMarkdownElem, + //MsgConstant.KELEMTYPEMULTIFORWARD to MsgElementConverter::convertXmlMultiMsgElem, + //MsgConstant.KELEMTYPESTRUCTLONGMSG to MsgElementConverter::convertXmlLongMsgElem, + MsgConstant.KELEMTYPEFACEBUBBLE to MsgElementConverter::convertBubbleFaceElem, + MsgConstant.KELEMTYPEINLINEKEYBOARD to MsgElementConverter::convertInlineKeyboardElem, + ) + + operator fun get(type: Int): IMsgElementConverter? = convertMap[type] + + /** + * 文本 / 艾特 消息转换消息段 + */ + private suspend fun convertTextElem( + chatType: Int, + peerId: String, + subPeer: String, + element: MsgElement + ): MessageSegment { + val text = element.textElement + return if (text.atType != MsgConstant.ATTYPEUNKNOWN) { + MessageSegment( + type = "at", + data = hashMapOf( + "qq" to ContactHelper.getUinByUidAsync(text.atNtUid), + ) + ) + } else { + MessageSegment( + type = "text", + data = hashMapOf( + "text" to text.content + ) + ) + } + } + + /** + * 小表情 / 戳一戳 消息转换消息段 + */ + private suspend fun convertFaceElem( + chatType: Int, + peerId: String, + subPeer: String, + element: MsgElement + ): MessageSegment { + val face = element.faceElement + + if (face.faceType == 5) { + return MessageSegment( + type = "poke", + data = hashMapOf( + "type" to face.pokeType, + "id" to face.vaspokeId, + "strength" to face.pokeStrength + ) + ) + } + when (face.faceIndex) { + 114 -> { + return MessageSegment( + type = "basketball", + data = hashMapOf( + "id" to face.resultId.ifEmpty { "0" }.toInt(), + ) + ) + } + + 358 -> { + if (face.sourceType == 1) return MessageSegment("new_dice") + return MessageSegment( + type = "new_dice", + data = hashMapOf( + "id" to face.resultId.ifEmpty { "0" }.toInt() + ) + ) + } + + 359 -> { + if (face.resultId.isEmpty()) return MessageSegment("new_rps") + return MessageSegment( + type = "new_rps", + data = hashMapOf( + "id" to face.resultId.ifEmpty { "0" }.toInt() + ) + ) + } + + 394 -> { + //LogCenter.log(face.toString()) + return MessageSegment( + type = "face", + data = hashMapOf( + "id" to face.faceIndex, + "big" to (face.faceType == 3), + "result" to (face.resultId ?: "1") + ) + ) + } + + else -> return MessageSegment( + type = "face", + data = hashMapOf( + "id" to face.faceIndex, + "big" to (face.faceType == 3) + ) + ) + } + } + + /** + * 图片消息转换消息段 + */ + private suspend fun convertImageElem( + chatType: Int, + peerId: String, + subPeer: String, + element: MsgElement + ): MessageSegment { + val image = element.picElement + val md5 = image.md5HexStr ?: image.fileName + .replace("{", "") + .replace("}", "") + .replace("-", "").split(".")[0] + + ImageDB.getInstance().imageMappingDao().insert( + ImageMapping(md5.uppercase(), chatType, image.fileSize) + ) + + //LogCenter.log(image.toString()) + + val originalUrl = image.originImageUrl ?: "" + //LogCenter.log({ "receive image: $image" }, Level.DEBUG) + + return MessageSegment( + type = "image", + data = hashMapOf( + "file" to md5, + "url" to when (chatType) { + MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl( + originalUrl, + md5 + ) + + MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(originalUrl, md5) + MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(originalUrl, md5) + else -> throw UnsupportedOperationException("Not supported chat type: $chatType") + }, + "subType" to image.picSubType, + "type" to if (image.isFlashPic == true) "flash" else if (image.original) "original" else "show" + ) + ) + } + + /** + * 语音消息转换消息段 + */ + private suspend fun convertVoiceElem( + chatType: Int, + peerId: String, + subPeer: String, + element: MsgElement + ): MessageSegment { + val record = element.pttElement + + val md5 = if (record.fileName.startsWith("silk")) + record.fileName.substring(5) + else record.md5HexStr + + return MessageSegment( + type = "record", + data = hashMapOf( + "file" to md5, + "url" to when (chatType) { + MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPttDownUrl( + "0", + record.md5HexStr, + record.fileUuid + ) + + MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPttDownUrl("0", record.fileUuid) + MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupPttDownUrl( + "0", + record.md5HexStr, + record.fileUuid + ) + + else -> throw UnsupportedOperationException("Not supported chat type: $chatType") + } + ).also { + if (record.voiceChangeType != MsgConstant.KPTTVOICECHANGETYPENONE) { + it["magic"] = "1" + } + if ((it["url"] as String).isBlank()) { + it.remove("url") + } + } + ) + } + + /** + * 视频消息转换消息段 + */ + private suspend fun convertVideoElem( + chatType: Int, + peerId: String, + subPeer: String, + element: MsgElement + ): MessageSegment { + val video = element.videoElement + val md5 = if (video.fileName.contains("/")) { + video.videoMd5.takeIf { + !it.isNullOrEmpty() + }?.hex2ByteArray() ?: video.fileName.split("/").let { + it[it.size - 2].hex2ByteArray() + } + } else video.fileName.split(".")[0].hex2ByteArray() + + //LogCenter.log({ "receive video msg: $video" }, Level.DEBUG) + + return MessageSegment( + type = "video", + data = hashMapOf( + "file" to video.fileName, + "url" to when (chatType) { + MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid) + MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", md5, video.fileUuid) + MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid) + else -> throw UnsupportedOperationException("Not supported chat type: $chatType") + } + ).also { + if ((it["url"] as String).isBlank()) + it.remove("url") + } + ) + } + + /** + * 商城大表情消息转换消息段 + */ + private suspend fun convertMarketFaceElem( + chatType: Int, + peerId: String, + subPeer: String, + element: MsgElement + ): MessageSegment { + val face = element.marketFaceElement + return when (face.emojiId.lowercase()) { + "4823d3adb15df08014ce5d6796b76ee1" -> MessageSegment("dice") + "83c8a293ae65ca140f348120a77448ee" -> MessageSegment("rps") + else -> MessageSegment( + type = "mface", + data = hashMapOf( + "id" to face.emojiId + ) + ) + } + } + + /** + * JSON消息转消息段 + */ + private suspend fun convertStructJsonElem( + chatType: Int, + peerId: String, + subPeer: String, + element: MsgElement + ): 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() + ) + ) + } + } + + /** + * 回复消息转消息段 + */ + private suspend fun convertReplyElem( + chatType: Int, + peerId: String, + subPeer: String, + element: MsgElement + ): MessageSegment { + val reply = element.replyElement + val msgId = reply.replayMsgId + val msgHash = if (msgId != 0L) { + MessageHelper.generateMsgIdHash(chatType, msgId) + } else { + MessageDB.getInstance().messageMappingDao() + .queryByMsgSeq(chatType, peerId, reply.replayMsgSeq?.toInt() ?: 0)?.msgHashId + ?: kotlin.run { + LogCenter.log("消息映射关系未找到: Message($reply)", Level.WARN) + MessageHelper.generateMsgIdHash(chatType, reply.sourceMsgIdInRecords) + } + } + + return MessageSegment( + type = "reply", + data = mapOf( + "id" to msgHash + ) + ) + } + + /** + * 灰色提示条消息过滤 + */ + private suspend fun convertGrayTipsElem( + chatType: Int, + peerId: String, + subPeer: String, + element: MsgElement + ): MessageSegment { + val tip = element.grayTipElement + when (tip.subElementType) { + MsgConstant.GRAYTIPELEMENTSUBTYPEJSON -> { + val notify = tip.jsonGrayTipElement + when (notify.busiId) { + /* 新人入群 */ 17L, /* 群戳一戳 */1061L, + /* 群撤回 */1014L, /* 群设精消息 */2401L, + /* 群头衔 */2407L -> { + } + + else -> LogCenter.log("不支持的灰条类型(JSON): ${notify.busiId}", Level.WARN) + } + } + + MsgConstant.GRAYTIPELEMENTSUBTYPEXMLMSG -> { + val notify = tip.xmlElement + when (notify.busiId) { + /* 群戳一戳 */1061L, /* 群打卡 */1068L -> {} + else -> LogCenter.log("不支持的灰条类型(XML): ${notify.busiId}", Level.WARN) + } + } + + else -> LogCenter.log("不支持的提示类型: ${tip.subElementType}", Level.WARN) + } + // 提示类消息,这里提供的是一个xml,不具备解析通用性 + // 在这里不推送 + throw UnknownError() + } + + /** + * 文件消息转换消息段 + */ + private suspend fun convertFileElem( + chatType: Int, + peerId: String, + subPeer: String, + element: MsgElement + ): MessageSegment { + val fileMsg = element.fileElement + val fileName = fileMsg.fileName + val fileSize = fileMsg.fileSize + val expireTime = fileMsg.expireTime ?: 0 + val fileId = fileMsg.fileUuid + val bizId = fileMsg.fileBizId ?: 0 + val fileSubId = fileMsg.fileSubId ?: "" + val url = when (chatType) { + MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId) + MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildFileDownUrl(peerId, subPeer, fileId, bizId) + else -> RichProtoSvc.getGroupFileDownUrl(peerId.toLong(), fileId, bizId) + } + + return MessageSegment( + type = "file", + data = mapOf( + "name" to fileName, + "size" to fileSize, + "expire" to expireTime, + "id" to fileId, + "url" to url, + "biz" to bizId, + "sub" to fileSubId + ) + ) + } + + /** + * 老板QQ的合并转发信息 + */ + private suspend fun convertXmlMultiMsgElem( + chatType: Int, + peerId: String, + subPeer: String, + element: MsgElement + ): MessageSegment { + val multiMsg = element.multiForwardMsgElement + return MessageSegment( + type = "forward", + data = mapOf( + "id" to multiMsg.resId + ) + ) + } + + private suspend fun convertXmlLongMsgElem( + chatType: Int, + peerId: String, + subPeer: String, + element: MsgElement + ): MessageSegment { + val longMsg = element.structLongMsgElement + return MessageSegment( + type = "forward", + data = mapOf( + "id" to longMsg.resId + ) + ) + } + + private suspend fun convertMarkdownElem( + chatType: Int, + peerId: String, + subPeer: String, + element: MsgElement + ): MessageSegment { + val markdown = element.markdownElement + return MessageSegment( + type = "markdown", + data = mapOf( + "content" to markdown.content + ) + ) + } + + private suspend fun convertBubbleFaceElem( + chatType: Int, + peerId: String, + subPeer: String, + element: MsgElement + ): MessageSegment { + val bubbleElement = element.faceBubbleElement + return MessageSegment( + type = "bubble_face", + data = mapOf( + "id" to bubbleElement.yellowFaceInfo.index, + "count" to (bubbleElement.faceCount ?: 1), + ) + ) + } + + private suspend fun convertInlineKeyboardElem( + chatType: Int, + peerId: String, + subPeer: String, + element: MsgElement + ): MessageSegment { + val keyboard = element.inlineKeyboardElement + return MessageSegment( + type = "inline_keyboard", + data = mapOf( + "data" to buildJsonObject { + putJsonArray("rows") { + keyboard.rows.forEach { row -> + add(buildJsonObject row@{ + putJsonArray("buttons") { + row.buttons.forEach { button -> + add(buildJsonObject { + put("id", button.id ?: "") + put("label", button.label ?: "") + put("visited_label", button.visitedLabel ?: "") + put("style", button.style) + put("type", button.type) + put("click_limit", button.clickLimit) + put("unsupport_tips", button.unsupportTips ?: "") + put("data", button.data) + put("at_bot_show_channel_list", button.atBotShowChannelList) + put("permission_type", button.permissionType) + putJsonArray("specify_role_ids") { + button.specifyRoleIds?.forEach { add(it) } + } + putJsonArray("specify_tinyids") { + button.specifyTinyids?.forEach { add(it) } + } + }) + } + } + }) + } + } + put("bot_appid", keyboard.botAppid) + }.toString() + ) + ) + } +} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/MsgElementMaker.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/msgelement/MsgElementMaker.kt similarity index 94% rename from xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/MsgElementMaker.kt rename to xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/msgelement/MsgElementMaker.kt index b8785a3..61dac15 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/MsgElementMaker.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/msgelement/MsgElementMaker.kt @@ -1,4 +1,4 @@ -package moe.fuqiuluo.qqinterface.servlet.msg +package moe.fuqiuluo.qqinterface.servlet.msg.msgelement import android.graphics.BitmapFactory import androidx.exifinterface.media.ExifInterface @@ -8,25 +8,7 @@ import com.tencent.mobileqq.pb.ByteStringMicro import com.tencent.mobileqq.qroute.QRoute import com.tencent.qphone.base.remote.ToServiceMsg import com.tencent.qqnt.aio.adapter.api.IAIOPttApi -import com.tencent.qqnt.kernel.nativeinterface.ArkElement -import com.tencent.qqnt.kernel.nativeinterface.FaceBubbleElement -import com.tencent.qqnt.kernel.nativeinterface.FaceElement -import com.tencent.qqnt.kernel.nativeinterface.InlineKeyboardButton -import com.tencent.qqnt.kernel.nativeinterface.InlineKeyboardElement -import com.tencent.qqnt.kernel.nativeinterface.InlineKeyboardRow -import com.tencent.qqnt.kernel.nativeinterface.MarkdownElement -import com.tencent.qqnt.kernel.nativeinterface.MarketFaceElement -import com.tencent.qqnt.kernel.nativeinterface.MarketFaceSupportSize -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import com.tencent.qqnt.kernel.nativeinterface.MsgElement -import com.tencent.qqnt.kernel.nativeinterface.PicElement -import com.tencent.qqnt.kernel.nativeinterface.PttElement -import com.tencent.qqnt.kernel.nativeinterface.QQNTWrapperUtil -import com.tencent.qqnt.kernel.nativeinterface.ReplyElement -import com.tencent.qqnt.kernel.nativeinterface.RichMediaFilePathInfo -import com.tencent.qqnt.kernel.nativeinterface.SmallYellowFaceInfo -import com.tencent.qqnt.kernel.nativeinterface.TextElement -import com.tencent.qqnt.kernel.nativeinterface.VideoElement +import com.tencent.qqnt.kernel.nativeinterface.* import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject @@ -37,6 +19,7 @@ import moe.fuqiuluo.qqinterface.servlet.LbsSvc import moe.fuqiuluo.qqinterface.servlet.ark.ArkAppInfo import moe.fuqiuluo.qqinterface.servlet.ark.ArkMsgSvc import moe.fuqiuluo.qqinterface.servlet.ark.WeatherSvc +import moe.fuqiuluo.qqinterface.servlet.transfile.* import moe.fuqiuluo.qqinterface.servlet.transfile.FileTransfer import moe.fuqiuluo.qqinterface.servlet.transfile.PictureResource import moe.fuqiuluo.qqinterface.servlet.transfile.Private @@ -44,8 +27,6 @@ import moe.fuqiuluo.qqinterface.servlet.transfile.Transfer import moe.fuqiuluo.qqinterface.servlet.transfile.Troop import moe.fuqiuluo.qqinterface.servlet.transfile.VideoResource import moe.fuqiuluo.qqinterface.servlet.transfile.VoiceResource -import moe.fuqiuluo.qqinterface.servlet.transfile.trans -import moe.fuqiuluo.qqinterface.servlet.transfile.with import moe.fuqiuluo.shamrock.helper.ActionMsgException import moe.fuqiuluo.shamrock.helper.ContactHelper import moe.fuqiuluo.shamrock.helper.IllegalParamsException @@ -56,16 +37,7 @@ import moe.fuqiuluo.shamrock.helper.LogicException import moe.fuqiuluo.shamrock.helper.MessageHelper import moe.fuqiuluo.shamrock.helper.MusicHelper import moe.fuqiuluo.shamrock.helper.ParamsException -import moe.fuqiuluo.shamrock.tools.asBoolean -import moe.fuqiuluo.shamrock.tools.asBooleanOrNull -import moe.fuqiuluo.shamrock.tools.asInt -import moe.fuqiuluo.shamrock.tools.asIntOrNull -import moe.fuqiuluo.shamrock.tools.asJsonArray -import moe.fuqiuluo.shamrock.tools.asJsonObject -import moe.fuqiuluo.shamrock.tools.asLong -import moe.fuqiuluo.shamrock.tools.asString -import moe.fuqiuluo.shamrock.tools.asStringOrNull -import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty +import moe.fuqiuluo.shamrock.tools.* import moe.fuqiuluo.shamrock.utils.AudioUtils import moe.fuqiuluo.shamrock.utils.FileUtils import moe.fuqiuluo.shamrock.utils.MediaType @@ -82,10 +54,10 @@ import kotlin.math.roundToInt import kotlin.random.Random import kotlin.random.nextInt -internal typealias IMsgMaker = suspend (Int, Long, String, JsonObject) -> Result +internal typealias IMsgElementMaker = suspend (Int, Long, String, JsonObject) -> Result internal object MsgElementMaker { - private val makerArray = hashMapOf( + private val makerMap = hashMapOf( "text" to MsgElementMaker::createTextElem, "face" to MsgElementMaker::createFaceElem, "pic" to MsgElementMaker::createImageElem, @@ -116,6 +88,8 @@ internal object MsgElementMaker { "inline_keyboard" to MsgElementMaker::createInlineKeywordElem ) + operator fun get(type: String): IMsgElementMaker? = makerMap[type] + private suspend fun createInlineKeywordElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result { fun tryNewKeyboardButton(btn: JsonObject): InlineKeyboardButton { return runCatching { @@ -1028,6 +1002,4 @@ internal object MsgElementMaker { if (!containsKey(it)) throw ParamsException(it) } } - - operator fun get(type: String): IMsgMaker? = makerArray[type] } \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/MessageHelper.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/MessageHelper.kt index 01590fa..0e09b35 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/MessageHelper.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/MessageHelper.kt @@ -16,13 +16,12 @@ import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonObject import moe.fuqiuluo.qqinterface.servlet.MsgSvc -import moe.fuqiuluo.qqinterface.servlet.msg.MessageElementMaker -import moe.fuqiuluo.qqinterface.servlet.msg.MsgElementMaker +import moe.fuqiuluo.qqinterface.servlet.msg.messageelement.MessageElementMaker +import moe.fuqiuluo.qqinterface.servlet.msg.msgelement.MsgElementMaker import moe.fuqiuluo.shamrock.helper.db.MessageDB import moe.fuqiuluo.shamrock.helper.db.MessageMapping import moe.fuqiuluo.shamrock.remote.structures.SendMsgResult import moe.fuqiuluo.shamrock.tools.EmptyJsonObject -import moe.fuqiuluo.shamrock.tools.asJsonObject import moe.fuqiuluo.shamrock.tools.asJsonObjectOrNull import moe.fuqiuluo.shamrock.tools.asString import moe.fuqiuluo.shamrock.tools.json @@ -52,7 +51,14 @@ internal object MessageHelper { return sendMessageWithoutMsgId(chatType, peerId, msg, fromId, callback) } - suspend fun resendMsg(chatType: Int, peerId: String, fromId: String, msgId: Long, retryCnt: Int, msgHashId: Int): Result { + suspend fun resendMsg( + chatType: Int, + peerId: String, + fromId: String, + msgId: Long, + retryCnt: Int, + msgHashId: Int + ): Result { val contact = generateContact(chatType, peerId, fromId) return resendMsg(contact, msgId, retryCnt, msgHashId) } @@ -61,11 +67,11 @@ internal object MessageHelper { if (retryCnt < 0) return Result.failure(SendMsgException("消息发送超时次数过多")) val service = QRoute.api(IMsgService::class.java) val result = withTimeoutOrNull(15000) { - if(suspendCancellableCoroutine { - service.resendMsg(contact, msgId) { result, _ -> - it.resume(result) - } - } != 0) { + if (suspendCancellableCoroutine { + service.resendMsg(contact, msgId) { result, _ -> + it.resume(result) + } + } != 0) { resendMsg(contact, msgId, retryCnt - 1, msgHashId) } else { Result.success(SendMsgResult(msgHashId, msgId, System.currentTimeMillis())) @@ -244,10 +250,11 @@ internal object MessageHelper { } suspend fun generateContact(chatType: Int, id: String, subId: String = ""): Contact { - val peerId = when(chatType) { + val peerId = when (chatType) { MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> { ContactHelper.getUidByUinAsync(id.toLong()) } + else -> id } return if (chatType == MsgConstant.KCHATTYPEGUILD) { @@ -277,7 +284,12 @@ internal object MessageHelper { } } - suspend fun messageArrayToMsgElements(chatType: Int, msgId: Long, targetUin: String, messageList: JsonArray): Pair> { + suspend fun messageArrayToMsgElements( + chatType: Int, + msgId: Long, + targetUin: String, + messageList: JsonArray + ): Pair> { val msgList = arrayListOf() var hasActionMsg = false messageList.forEach { @@ -306,7 +318,12 @@ internal object MessageHelper { return hasActionMsg to msgList } - suspend fun messageArrayToMessageElements(chatType: Int, msgId: Long, targetUin: String, messageList: JsonArray): Pair> { + suspend fun messageArrayToMessageElements( + chatType: Int, + msgId: Long, + targetUin: String, + messageList: JsonArray + ): Pair> { val msgList = arrayListOf() var hasActionMsg = false messageList.forEach { @@ -419,22 +436,6 @@ internal object MessageHelper { return arrayList.jsonArray } - fun encodeCQCode(msg: List>): String { - return nativeEncodeCQCode(msg.map { - val params = hashMapOf() - it.forEach { (key, value) -> - if (key != "type") { - value.asJsonObject.forEach { param, element -> - params[param] = element.asString - } - } else { - params["_type"] = value.asString - } - } - params - }) - } - private external fun nativeDecodeCQCode(code: String): List> - private external fun nativeEncodeCQCode(segment: List>): String + external fun nativeEncodeCQCode(segment: List>): String } \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetForwardMsg.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetForwardMsg.kt index 231256d..54a2522 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetForwardMsg.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetForwardMsg.kt @@ -1,21 +1,17 @@ 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.IActionHandler import moe.fuqiuluo.shamrock.remote.service.data.MessageDetail -import moe.fuqiuluo.shamrock.remote.service.data.MessageSender import moe.fuqiuluo.shamrock.tools.EmptyJsonString import moe.fuqiuluo.symbols.OneBotHandler @OneBotHandler("get_forward_msg") -internal object GetForwardMsg: IActionHandler() { +internal object GetForwardMsg : IActionHandler() { override suspend fun internalHandle(session: ActionSession): String { val id = session.getString("id") return invoke(id, session.echo) @@ -25,32 +21,8 @@ internal object GetForwardMsg: IActionHandler() { 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 - .ifEmpty { msg.sendMemberName } - .ifEmpty { msg.sendRemarkName } - .ifEmpty { msg.peerName }, "unknown", 0, msg.senderUid, 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) + val result = MsgSvc.getMultiMsg(resId).getOrElse { return logic(it.toString(), echo) } + return ok(data = GetForwardMsgResult(result), echo = echo) } @Serializable diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetHistoryMsg.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetHistoryMsg.kt index 1e051c1..13ca2db 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetHistoryMsg.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetHistoryMsg.kt @@ -6,7 +6,8 @@ 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.qqinterface.servlet.msg.msgelement.toSegments +import moe.fuqiuluo.qqinterface.servlet.msg.toListMap import moe.fuqiuluo.shamrock.helper.MessageHelper import moe.fuqiuluo.shamrock.helper.db.MessageDB import moe.fuqiuluo.shamrock.remote.action.ActionSession @@ -21,7 +22,7 @@ import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine @OneBotHandler("get_history_msg") -internal object GetHistoryMsg: IActionHandler() { +internal object GetHistoryMsg : IActionHandler() { override suspend fun internalHandle(session: ActionSession): String { val msgType = session.getString("message_type") val peerId = session.getString(if (msgType == "group") "group_id" else "user_id") @@ -64,10 +65,10 @@ internal object GetHistoryMsg: IActionHandler() { qqMsgId = msg.msgId, chatType = msg.chatType, subChatType = msg.chatType, - peerId = peerId, + peerId = msg.peerUin.toString(), msgSeq = msg.msgSeq.toInt(), time = msg.msgTime, - subPeerId = msg.channelId ?: peerId + subPeerId = msg.channelId ?: msg.peerUin.toString() ) MessageDetail( time = msg.msgTime.toInt(), @@ -77,9 +78,11 @@ internal object GetHistoryMsg: IActionHandler() { sender = MessageSender( msg.senderUin, msg.sendNickName, "unknown", 0, msg.senderUid, msg.senderUid ), - message = MessageConvert.convertMessageRecordToMsgSegment(msg).map { - it.toJson() - }, + message = msg.elements.toSegments( + msg.chatType, + if (msg.chatType == MsgConstant.KCHATTYPEGUILD) msg.guildId else msg.peerUin.toString(), + msg.channelId ?: msg.peerUin.toString() + ).toListMap(), peerId = msg.peerUin, groupId = if (msg.chatType == MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0, targetId = if (msg.chatType != MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0 @@ -101,13 +104,15 @@ internal object GetHistoryMsg: IActionHandler() { .ifEmpty { msg.sendRemarkName } .ifEmpty { msg.peerName }, "unknown", 0, msg.senderUid, msg.senderUid ), - message = MessageConvert.convertMessageRecordToMsgSegment(msg).map { - it.toJson() - }, + message = msg.elements.toSegments( + chatType, + if (chatType == MsgConstant.KCHATTYPEGUILD) msg.guildId else msg.peerUin.toString(), + msg.channelId ?: peerId).toListMap(), peerId = msg.peerUin, groupId = if (msg.chatType == MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0, targetId = if (msg.chatType != MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0 - )) + ) + ) } } diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetMsg.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetMsg.kt index 4038870..6e21c54 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetMsg.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetMsg.kt @@ -8,7 +8,8 @@ import moe.fuqiuluo.shamrock.remote.action.IActionHandler import moe.fuqiuluo.shamrock.remote.service.data.MessageDetail import moe.fuqiuluo.shamrock.remote.service.data.MessageSender import moe.fuqiuluo.qqinterface.servlet.MsgSvc -import moe.fuqiuluo.qqinterface.servlet.msg.convert.MessageConvert +import moe.fuqiuluo.qqinterface.servlet.msg.msgelement.toSegments +import moe.fuqiuluo.qqinterface.servlet.msg.toListMap import moe.fuqiuluo.shamrock.tools.EmptyJsonString import moe.fuqiuluo.symbols.OneBotHandler @@ -39,9 +40,11 @@ internal object GetMsg: IActionHandler() { msg.senderUid, msg.senderUid ), - message = MessageConvert.convertMessageRecordToMsgSegment(msg).map { - it.toJson() - }, + message = msg.elements.toSegments( + msg.chatType, + if (msg.chatType == MsgConstant.KCHATTYPEGUILD) msg.guildId else msg.peerUin.toString(), + msg.channelId ?: msg.peerUin.toString() + ).toListMap(), peerId = msg.peerUin, groupId = if (msg.chatType == MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0, targetId = if (msg.chatType != MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0 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 7dfd4fb..8a336ee 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 @@ -4,7 +4,7 @@ import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import kotlinx.serialization.json.* import moe.fuqiuluo.qqinterface.servlet.MsgSvc import moe.fuqiuluo.qqinterface.servlet.TicketSvc -import moe.fuqiuluo.qqinterface.servlet.msg.convert.toSegments +import moe.fuqiuluo.qqinterface.servlet.msg.msgelement.toSegments import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.MessageHelper @@ -128,8 +128,12 @@ internal object SendForwardMessage : IActionHandler() { record.chatType, record.msgId, record.peerUin.toString(), - record.elements.toSegments(record.chatType, record.peerUin.toString(), "0").also { - desc[++i] = record.peerName + ": " + record.elements.toSegments( + record.chatType, + record.peerUin.toString(), + "0" + ).also { + desc[++i] = record.sendMemberName.ifEmpty { record.sendNickName } + ": " }.map { when (it.type) { "text" -> desc[i] += it.data["text"] as String @@ -142,7 +146,6 @@ internal object SendForwardMessage : IActionHandler() { "node" -> desc[i] += "[合并转发消息]" } - it.toJson() }.json ).also { @@ -157,10 +160,10 @@ internal object SendForwardMessage : IActionHandler() { peerUid = data["uid"]?.asString ?: TicketSvc.getUid() ), content = MessageContent( - msgType = 529, + msgType = 166, msgViaRandom = 4, - msgSeq = data["seq"]?.asLong ?: 0, - msgTime = System.currentTimeMillis() / 1000, + msgSeq = data["seq"]?.asLong ?: Random.nextLong(), + msgTime = data["time"]?.asLong ?: (System.currentTimeMillis() / 1000), u2 = 1, u6 = 0, u7 = 0, @@ -221,7 +224,8 @@ internal object SendForwardMessage : IActionHandler() { chatType, peerId, listOf( hashMapOf( - "type" to "json", "data" to hashMapOf( + "type" to "json", + "data" to hashMapOf( "data" to hashMapOf( "app" to "com.tencent.multimsg", "config" to hashMapOf( @@ -235,7 +239,7 @@ internal object SendForwardMessage : IActionHandler() { "extra" to hashMapOf( "filename" to uniseq, "tsum" to 2 - ).json.toString() + "\n", + ).json.toString(), "meta" to hashMapOf( "detail" to hashMapOf( "news" to desc.slice(0..if (i < 3) i else 3) diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/TestAction.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/TestAction.kt index 99d6044..bae396c 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/TestAction.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/TestAction.kt @@ -1,27 +1,9 @@ package moe.fuqiuluo.shamrock.remote.api -import com.tencent.qqnt.kernel.nativeinterface.Contact -import com.tencent.qqnt.kernel.nativeinterface.IMsgOperateCallback -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import com.tencent.qqnt.kernel.nativeinterface.MsgElement -import com.tencent.qqnt.kernel.nativeinterface.MsgRecord -import com.tencent.qqnt.kernel.nativeinterface.TextElement -import io.ktor.server.application.call -import io.ktor.server.response.respondText import io.ktor.server.routing.Routing -import io.ktor.server.routing.get -import moe.fuqiuluo.qqinterface.servlet.TicketSvc -import moe.fuqiuluo.qqinterface.servlet.msg.convert.toCQCode import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.helper.MessageHelper import moe.fuqiuluo.shamrock.tools.ShamrockVersion -import moe.fuqiuluo.shamrock.tools.fetch -import moe.fuqiuluo.shamrock.tools.fetchOrThrow -import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher -import java.util.ArrayList -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine fun Routing.testAction() { if(ShamrockVersion.contains("dev")) { diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/api/GlobalEventTransmitter.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/api/GlobalEventTransmitter.kt index e1a2524..832be10 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/api/GlobalEventTransmitter.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/api/GlobalEventTransmitter.kt @@ -6,14 +6,14 @@ import com.tencent.qqnt.kernel.nativeinterface.MsgElement import com.tencent.qqnt.kernel.nativeinterface.MsgRecord import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch import moe.fuqiuluo.qqinterface.servlet.BaseSvc import moe.fuqiuluo.qqinterface.servlet.CardSvc import moe.fuqiuluo.qqinterface.servlet.GroupSvc -import moe.fuqiuluo.qqinterface.servlet.msg.convert.toSegments +import moe.fuqiuluo.qqinterface.servlet.msg.msgelement.toSegments +import moe.fuqiuluo.qqinterface.servlet.msg.toJson import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig import moe.fuqiuluo.shamrock.remote.service.data.push.GroupFileMsg import moe.fuqiuluo.shamrock.remote.service.data.push.MemberRole @@ -80,9 +80,7 @@ internal object GlobalEventTransmitter: BaseSvc() { peerId = uin, userId = record.senderUin, message = if(ShamrockConfig.useCQ()) rawMsg.json - else elements.toSegments(record.chatType, record.peerUin.toString(), "0").map { - it.toJson() - }.json, + else elements.toSegments(record.chatType, record.peerUin.toString(), "0").toJson(), rawMessage = rawMsg, font = 0, sender = Sender( @@ -137,9 +135,7 @@ internal object GlobalEventTransmitter: BaseSvc() { peerId = botUin, userId = record.senderUin, message = if(ShamrockConfig.useCQ()) rawMsg.json - else elements.toSegments(record.chatType, record.peerUin.toString(), "0").map { - it.toJson() - }.json, + else elements.toSegments(record.chatType, record.peerUin.toString(), "0").toJson(), rawMessage = rawMsg, font = 0, sender = Sender( @@ -187,15 +183,13 @@ internal object GlobalEventTransmitter: BaseSvc() { messageId = msgHash, targetId = record.peerUin, peerId = botUin, - userId = record.senderUid.toLong(), + userId = record.senderUin, message = if(ShamrockConfig.useCQ()) rawMsg.json - else elements.toSegments(record.chatType, record.guildId, record.channelId).map { - it.toJson() - }.json, + else elements.toSegments(record.chatType, record.guildId, record.channelId).toJson(), rawMessage = rawMsg, font = 0, sender = Sender( - userId = record.senderUid.toLong(), + userId = record.senderUin, nickname = nickName, card = record.sendMemberName, role = MemberRole.Member, // TODO(GUILD ROLE) diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/AioListener.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/AioListener.kt index 2b418c1..272e111 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/AioListener.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/AioListener.kt @@ -9,7 +9,7 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import moe.fuqiuluo.qqinterface.servlet.MsgSvc import moe.fuqiuluo.qqinterface.servlet.TicketSvc -import moe.fuqiuluo.qqinterface.servlet.msg.convert.toCQCode +import moe.fuqiuluo.qqinterface.servlet.msg.msgelement.toCQCode import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig import moe.fuqiuluo.shamrock.helper.Level