From 7ec4a95303b8e2ed4ce1a175220fdecc43ee6aa9 Mon Sep 17 00:00:00 2001 From: WhiteChi Date: Thu, 26 Oct 2023 17:01:31 +0800 Subject: [PATCH] =?UTF-8?q?`Shamrock`:=20=E4=BC=98=E5=8C=96=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E8=BD=AC=E6=8D=A2=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: WhiteChi --- .../shamrock/ui/fragment/DashboardFragment.kt | 3 +- .../qqinterface/servlet/ark/WeatherSvc.kt | 2 +- .../qqinterface/servlet/msg/MessageConvert.kt | 305 ----------------- .../servlet/msg/convert/MessageConvert.kt | 116 +++++++ .../msg/convert/MessageElemConverter.kt | 318 ++++++++++++++++++ .../fuqiuluo/shamrock/helper/MessageHelper.kt | 2 +- .../fuqiuluo/shamrock/remote/HTTPServer.kt | 2 +- .../shamrock/remote/action/handlers/GetMsg.kt | 6 +- .../shamrock/remote/service/HttpService.kt | 6 +- .../remote/service/WebSocketClientService.kt | 9 +- .../remote/service/WebSocketService.kt | 6 +- .../remote/service/config/ShamrockConfig.kt | 5 + .../shamrock/remote/service/data/Message.kt | 2 +- .../remote/service/listener/AioListener.kt | 2 +- 14 files changed, 463 insertions(+), 321 deletions(-) delete mode 100644 xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/MessageConvert.kt create mode 100644 xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/convert/MessageConvert.kt create mode 100644 xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/convert/MessageElemConverter.kt diff --git a/app/src/main/java/moe/fuqiuluo/shamrock/ui/fragment/DashboardFragment.kt b/app/src/main/java/moe/fuqiuluo/shamrock/ui/fragment/DashboardFragment.kt index 9fdafb0..955a809 100644 --- a/app/src/main/java/moe/fuqiuluo/shamrock/ui/fragment/DashboardFragment.kt +++ b/app/src/main/java/moe/fuqiuluo/shamrock/ui/fragment/DashboardFragment.kt @@ -357,6 +357,7 @@ private fun FunctionCard( return@Function true } + /* Function( title = "专业级接口", desc = "如果你不知道你在做什么,请不要开启本功能。", @@ -366,7 +367,7 @@ private fun FunctionCard( ShamrockConfig.setPro(ctx, it) AppRuntime.log("专业级API = $it", Level.WARN) return@Function true - } + }*/ } } } diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/WeatherSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/WeatherSvc.kt index 2d21162..1868420 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/WeatherSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/ark/WeatherSvc.kt @@ -16,7 +16,7 @@ import moe.fuqiuluo.shamrock.tools.* import java.lang.Exception @Serializable -data class Region( +internal data class Region( val adcode: Int, val province: String?, val city: String? diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/MessageConvert.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/MessageConvert.kt deleted file mode 100644 index 67ea9fb..0000000 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/MessageConvert.kt +++ /dev/null @@ -1,305 +0,0 @@ -package moe.fuqiuluo.qqinterface.servlet.msg - -import moe.fuqiuluo.shamrock.helper.ContactHelper -import moe.fuqiuluo.shamrock.helper.MessageHelper -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 kotlinx.serialization.json.JsonObject -import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc -import moe.fuqiuluo.shamrock.tools.* -import moe.fuqiuluo.shamrock.helper.db.ImageDB -import moe.fuqiuluo.shamrock.helper.db.ImageMapping -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.helper.db.MessageDB - -internal typealias MsgSegment = ArrayList> - -internal suspend fun MsgRecord.toSegment(): MsgSegment { - return MsgConvert.convertMsgRecordToMsgSegment(this) -} - -internal suspend fun MsgRecord.toCQCode(): String { - return MsgConvert.convertMsgRecordToCQCode(this) -} - -internal suspend fun List.toSegment(chatType: Int, peerId: String): MsgSegment { - return MsgConvert.convertMsgElementsToMsgSegment(chatType, this, peerId) -} - -internal suspend fun List.toCQCode(chatType: Int, peerId: String): String { - return MsgConvert.convertMsgElementsToCQCode(this, chatType, peerId) -} - -internal object MsgConvert { - suspend fun convertMsgRecordToCQCode(record: MsgRecord, chatType: Int = record.chatType): String { - return MessageHelper.encodeCQCode(convertMsgElementsToMsgSegment( - chatType, - record.elements, - record.peerUin.toString() - )) - } - - suspend fun convertMsgElementsToCQCode( - elements: List, - chatType: Int, - peerId: String - ): String { - if(elements.isEmpty()) { - return "" - } - return MessageHelper.encodeCQCode(convertMsgElementsToMsgSegment(chatType, elements, peerId)) - } - - suspend fun convertMsgRecordToMsgSegment(record: MsgRecord, chatType: Int = record.chatType): ArrayList> { - return convertMsgElementsToMsgSegment(chatType, record.elements, record.peerUin.toString()) - } - - suspend fun convertMsgElementsToMsgSegment( - chatType: Int, - elements: List, - peerId: String - ): ArrayList> { - val messageData = arrayListOf>() - elements.forEach { - try { - val segment = covertMsgElementToMsgSegment(chatType, peerId, it) - if (segment != null) { - messageData.add(segment) - } - } catch (e: Throwable) { - LogCenter.log("消息element转换错误:$e", Level.WARN) - } - } - return messageData - } - - suspend fun covertMsgElementToMsgSegment(chatType: Int, peerId: String, element: MsgElement): HashMap? { - when (element.elementType) { - MsgConstant.KELEMTYPETEXT -> { - val text = element.textElement - return if (text.atType != MsgConstant.ATTYPEUNKNOWN) { - hashMapOf( - "type" to "at".json, - "data" to JsonObject(mapOf( - "qq" to ContactHelper.getUinByUidAsync(text.atNtUid).json, - )) - ) - } else { - hashMapOf( - "type" to "text".json, - "data" to JsonObject(mapOf( - "text" to text.content.json - )) - ) - } - } - MsgConstant.KELEMTYPEFACE -> { - val face = element.faceElement - if (face.faceType == 5) { - return hashMapOf( - "type" to "poke".json, - "data" to JsonObject(mapOf( - "type" to face.pokeType.json, - "id" to face.vaspokeId.json, - "strength" to face.pokeStrength.json - )) - ) - } - return hashMapOf( - "type" to "face".json, - "data" to JsonObject(mapOf( - "id" to face.faceIndex.json - )) - ) - } - MsgConstant.KELEMTYPEPIC -> { - 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) - ) - - return hashMapOf( - "type" to "image".json, - "data" to JsonObject(mapOf( - "file" to md5.json, - "url" to when(chatType) { - MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(md5) - MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(md5) - else -> error("Not supported chat type: $chatType, convertMsgElementsToMsgSegment::Pic") - }.json - )) - ) - } - MsgConstant.KELEMTYPEPTT -> { - val record = element.pttElement - - val md5 = if (record.fileName.startsWith("silk")) - record.fileName.substring(5) - else record.md5HexStr - - return hashMapOf( - "type" to "record".json, - "data" to JsonObject(hashMapOf( - "file" to md5.json, - "url" to when(chatType) { - MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPttDownUrl("0", record.md5HexStr, record.fileUuid) - MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPttDownUrl("0", record.fileUuid) - else -> error("Not supported chat type: $chatType, convertMsgElementsToMsgSegment::Pic") - }.json - ).also { - if(record.voiceChangeType != MsgConstant.KPTTVOICECHANGETYPENONE) { - it["magic"] = "1".json - } - if (it["url"].asString.isBlank()) { - it.remove("url") - } - }) - ) - } - MsgConstant.KELEMTYPEVIDEO -> { - val video = element.videoElement - return hashMapOf( - "type" to "video".json, - "data" to JsonObject(hashMapOf( - "file" to video.fileName.json, - "url" to when(chatType) { - MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupVideoDownUrl("0", video.fileName, video.fileUuid) - MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", video.fileName, video.fileUuid) - else -> error("Not supported chat type: $chatType, convertMsgElementsToMsgSegment::Pic") - }.json - ).also { - if (it["url"].asString.isBlank()) { - it.remove("url") - } - }) - ) - } - MsgConstant.KELEMTYPEMARKETFACE -> { - val face = element.marketFaceElement - when (face.emojiId.lowercase()) { - "4823d3adb15df08014ce5d6796b76ee1" -> return hashMapOf("type" to "dice".json) - "83c8a293ae65ca140f348120a77448ee" -> return hashMapOf("type" to "rps".json) - else -> LogCenter.log("暂不支持的超表: ${face.emojiId}", Level.WARN) - } - } - MsgConstant.KELEMTYPEARKSTRUCT -> { - try { - val data = element.arkElement.bytesData.asJsonObject - val app = data["app"].asString - when (app) { - "com.tencent.troopsharecard" -> { - val info = data["meta"].asJsonObject["contact"].asJsonObject - return hashMapOf( - "type" to "contact".json, - "data" to JsonObject(mapOf( - "type" to "group".json, - "id" to info["jumpUrl"].asString.split("group_code=")[1].json, - )) - ) - } - "com.tencent.contact.lua" -> { - val info = data["meta"].asJsonObject["contact"].asJsonObject - return hashMapOf( - "type" to "contact".json, - "data" to JsonObject(mapOf( - "type" to "private".json, - "id" to info["jumpUrl"].asString.split("uin=")[1].json, - )) - ) - } - /*"com.tencent.structmsg" -> { - val info = data["meta"].asJsonObject["news"].asJsonObject - return hashMapOf( - "type" to "share".json, - "data" to JsonObject(mapOf( - "url" to info["jumpUrl"]!!, - "title" to info["title"]!!, - "content" to info["desc"]!!, - "image" to info["preview"]!! - )) - ) - }*/ - "com.tencent.map" -> { - val info = data["meta"].asJsonObject["Location.Search"].asJsonObject - return hashMapOf( - "type" to "location".json, - "data" to JsonObject(mapOf( - "lat" to info["lat"]!!, - "lon" to info["lng"]!!, - "content" to info["address"]!!, - "title" to info["name"]!! - )) - ) - } - else -> { - return hashMapOf( - "type" to "json".json, - "data" to JsonObject(mapOf( - "data" to element.arkElement.bytesData.json, - )) - ) - } - } - } catch (e: Throwable) { - LogCenter.log(e.stackTraceToString(), Level.WARN) - } - } - MsgConstant.KELEMTYPEREPLY -> { - 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 - ?: MessageHelper.generateMsgIdHash(chatType, reply.sourceMsgIdInRecords) - } - - return hashMapOf( - "type" to "reply".json, - "data" to JsonObject(mapOf( - "id" to msgHash.json, - )) - ) - } - MsgConstant.KELEMTYPEGRAYTIP -> { - val tip = element.grayTipElement - when(val tipType = tip.subElementType) { - MsgConstant.GRAYTIPELEMENTSUBTYPEJSON -> { - val notify = tip.jsonGrayTipElement - when(notify.busiId) { - /* 新人入群 */ 17L, - /* 群戳一戳 */1061L, /* 群撤回 */1014L -> {} - else -> LogCenter.log("不支持的灰条类型(JSON): $tipType", Level.WARN) - } - return null - } - MsgConstant.GRAYTIPELEMENTSUBTYPEXMLMSG -> { - val notify = tip.xmlElement - when(notify.busiId) { - /* 群戳一戳 */12L -> {} - else -> LogCenter.log("不支持的灰条类型(XML): $tipType", Level.WARN) - } - return null - } - else -> LogCenter.log("不支持的提示类型: $tip", Level.WARN) - } - } - MsgConstant.KELEMTYPEFILE -> { - // TODO(自发消息 / 其他客户端同步文件消息处理?) - return null - } - else -> LogCenter.log("不支持的Elem转消息段: ${element.elementType}", Level.WARN) - } - return null - } - -} \ 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 new file mode 100644 index 0000000..d9e1cc7 --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/convert/MessageConvert.kt @@ -0,0 +1,116 @@ +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.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): MessageSegmentList { + return MessageConvert.convertMessageElementsToMsgSegment(chatType, this, peerId) +} + +internal suspend fun List.toCQCode(chatType: Int, peerId: String): String { + return MessageConvert.convertMsgElementsToCQCode(this, chatType, peerId) +} + + +internal object MessageConvert { + private val convertMap by lazy { + mutableMapOf( + MsgConstant.KELEMTYPETEXT to MessageElemConverter.TextConverter, + MsgConstant.KELEMTYPEFACE to MessageElemConverter.FaceConverter, + MsgConstant.KELEMTYPEPIC to MessageElemConverter.ImageConverter, + MsgConstant.KELEMTYPEPTT to MessageElemConverter.VoiceConverter, + MsgConstant.KELEMTYPEVIDEO to MessageElemConverter.VideoConverter, + MsgConstant.KELEMTYPEMARKETFACE to MessageElemConverter.MarketFaceConverter, + MsgConstant.KELEMTYPEARKSTRUCT to MessageElemConverter.StructJsonConverter, + MsgConstant.KELEMTYPEREPLY to MessageElemConverter.ReplyConverter, + MsgConstant.KELEMTYPEGRAYTIP to MessageElemConverter.GrayTipsConverter, + MsgConstant.KELEMTYPEFILE to MessageElemConverter.FileConverter + ) + } + + suspend fun convertMessageElementsToMsgSegment( + chatType: Int, + elements: List, + peerId: String + ): ArrayList { + val messageData = arrayListOf() + elements.forEach { + kotlin.runCatching { + val elementId = it.elementType + val converter = convertMap[elementId] + converter?.convert(chatType, peerId, it) + ?: throw UnsupportedOperationException("不支持的消息element类型:$elementId") + }.onSuccess { + messageData.add(it) + }.onFailure { + if (it is UnknownError) { + // 不处理的消息类型,抛出unknown error + } else { + LogCenter.log("消息element转换错误:$it", Level.WARN) + } + } + } + return messageData + } + + suspend fun convertMessageRecordToMsgSegment(record: MsgRecord, chatType: Int = record.chatType): ArrayList { + return convertMessageElementsToMsgSegment(chatType, record.elements, record.peerUin.toString()) + } + + suspend fun convertMsgElementsToCQCode( + elements: List, + chatType: Int, + peerId: String + ): String { + if(elements.isEmpty()) { + return "" + } + val msgList = convertMessageElementsToMsgSegment(chatType, elements, peerId).map { + it.toJson() + } + return MessageHelper.encodeCQCode(msgList) + } + + suspend fun convertMessageRecordToCQCode(record: MsgRecord, chatType: Int = record.chatType): String { + return MessageHelper.encodeCQCode( + convertMessageElementsToMsgSegment( + chatType, + record.elements, + record.peerUin.toString() + ).map { it.toJson() } + ) + } +} + +internal fun interface IMessageConvert { + suspend fun convert(chatType: Int, peerId: 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 new file mode 100644 index 0000000..582ee7b --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/convert/MessageElemConverter.kt @@ -0,0 +1,318 @@ +package moe.fuqiuluo.qqinterface.servlet.msg.convert + +import com.tencent.qqnt.kernel.nativeinterface.MsgConstant +import com.tencent.qqnt.kernel.nativeinterface.MsgElement +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.json + +internal sealed class MessageElemConverter: IMessageConvert { + /** + * 文本 / 艾特 消息转换消息段 + */ + object TextConverter: MessageElemConverter() { + override suspend fun convert(chatType: Int, peerId: 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 + ) + ) + } + } + } + + /** + * 小表情 / 戳一戳 消息转换消息段 + */ + object FaceConverter: MessageElemConverter() { + override suspend fun convert(chatType: Int, peerId: 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 + ) + ) + } + return MessageSegment( + type = "face", + data = hashMapOf( + "id" to face.faceIndex + ) + ) + } + } + + /** + * 图片消息转换消息段 + */ + object ImageConverter: MessageElemConverter() { + override suspend fun convert( + chatType: Int, + peerId: 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) + ) + + return MessageSegment( + type = "image", + data = hashMapOf( + "file" to md5, + "url" to when(chatType) { + MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(md5) + MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(md5) + else -> unknownChatType(chatType) + } + ) + ) + } + } + + /** + * 语音消息转换消息段 + */ + object VoiceConverter: MessageElemConverter() { + override suspend fun convert( + chatType: Int, + peerId: 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) + else -> unknownChatType(chatType) + } + ).also { + if(record.voiceChangeType != MsgConstant.KPTTVOICECHANGETYPENONE) { + it["magic"] = "1".json + } + if ((it["url"] as String).isBlank()) { + it.remove("url") + } + } + ) + } + } + + /** + * 视频消息转换消息段 + */ + object VideoConverter: MessageElemConverter() { + override suspend fun convert( + chatType: Int, + peerId: String, + element: MsgElement + ): MessageSegment { + val video = element.videoElement + return MessageSegment( + type = "video", + data = hashMapOf( + "file" to video.fileName, + "url" to when(chatType) { + MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupVideoDownUrl("0", video.fileName, video.fileUuid) + MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", video.fileName, video.fileUuid) + else -> unknownChatType(chatType) + } + ).also { + if ((it["url"] as String).isBlank()) + it.remove("url") + } + ) + } + } + + /** + * 商城大表情消息转换消息段 + */ + object MarketFaceConverter: MessageElemConverter() { + override suspend fun convert( + chatType: Int, + peerId: 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消息转消息段 + */ + object StructJsonConverter: MessageElemConverter() { + override suspend fun convert( + chatType: Int, + peerId: String, + element: MsgElement + ): MessageSegment { + val data = element.arkElement.bytesData.asJsonObject + return when (data["app"].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 + ) + ) + } + } + } + + /** + * 回复消息转消息段 + */ + object ReplyConverter: MessageElemConverter() { + override suspend fun convert( + chatType: Int, + peerId: 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 + ?: MessageHelper.generateMsgIdHash(chatType, reply.sourceMsgIdInRecords) + } + + return MessageSegment( + type = "reply", + data = mapOf( + "id" to msgHash + ) + ) + } + } + + /** + * 灰色提示条消息过滤 + */ + object GrayTipsConverter: MessageElemConverter() { + override suspend fun convert( + chatType: Int, + peerId: String, + element: MsgElement + ): MessageSegment { + val tip = element.grayTipElement + when(val tipType = tip.subElementType) { + MsgConstant.GRAYTIPELEMENTSUBTYPEJSON -> { + val notify = tip.jsonGrayTipElement + when(notify.busiId) { + /* 新人入群 */ 17L, + /* 群戳一戳 */1061L, /* 群撤回 */1014L -> {} + else -> LogCenter.log("不支持的灰条类型(JSON): $tipType", Level.WARN) + } + } + MsgConstant.GRAYTIPELEMENTSUBTYPEXMLMSG -> { + val notify = tip.xmlElement + when(notify.busiId) { + /* 群戳一戳 */12L -> {} + else -> LogCenter.log("不支持的灰条类型(XML): $tipType", Level.WARN) + } + } + else -> LogCenter.log("不支持的提示类型: $tip", Level.WARN) + } + // 提示类消息,这里提供的是一个xml,不具备解析通用性 + // 在这里不推送 + throw UnknownError() + } + } + + /** + * 文件消息转换消息段 + */ + object FileConverter: MessageElemConverter() { + override suspend fun convert( + chatType: Int, + peerId: String, + element: MsgElement + ): MessageSegment { + // 使用其他地方的推送,而不是使用消息 + throw UnknownError() + } + } + + protected fun unknownChatType(chatType: Int) { + throw UnsupportedOperationException("Not supported chat type: $chatType") + } +} 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 cc88fce..58b6f78 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/MessageHelper.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/MessageHelper.kt @@ -169,7 +169,7 @@ internal object MessageHelper { return arrayList.jsonArray } - fun encodeCQCode(msg: ArrayList>): String { + fun encodeCQCode(msg: List>): String { return nativeEncodeCQCode(msg.map { val params = hashMapOf() it.forEach { (key, value) -> diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/HTTPServer.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/HTTPServer.kt index 90bfa83..7ef6509 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/HTTPServer.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/HTTPServer.kt @@ -62,7 +62,7 @@ internal object HTTPServer { weatherAction() otherAction() guildAction() - if (ShamrockConfig.isPro()) { + if (ShamrockConfig.isDev()) { qsign() obtainProtocolData() } 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 56b20ef..b6dcfce 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,7 @@ 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.MsgConvert +import moe.fuqiuluo.qqinterface.servlet.msg.convert.MessageConvert import moe.fuqiuluo.shamrock.tools.EmptyJsonString internal object GetMsg: IActionHandler() { @@ -31,7 +31,9 @@ internal object GetMsg: IActionHandler() { sender = MessageSender( msg.senderUin, msg.sendNickName, "unknown", 0, msg.senderUid ), - message = MsgConvert.convertMsgRecordToMsgSegment(msg), + 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 diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/HttpService.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/HttpService.kt index 75c0370..7a4cd98 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/HttpService.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/HttpService.kt @@ -17,6 +17,7 @@ import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonPrimitive import moe.fuqiuluo.qqinterface.servlet.TicketSvc import moe.fuqiuluo.qqinterface.servlet.msg.* +import moe.fuqiuluo.qqinterface.servlet.msg.convert.toSegments import moe.fuqiuluo.shamrock.remote.service.api.HttpPushServlet import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig import moe.fuqiuluo.shamrock.remote.service.data.push.* @@ -308,7 +309,10 @@ internal object HttpService: HttpPushServlet() { targetId = if(msgType != MsgType.Private) 0 else record.peerUin, peerId = if (record.senderUin == uin) record.peerUin else uin, userId = record.senderUin, - message = if(ShamrockConfig.useCQ()) raw.json else elements.toSegment(record.chatType, record.peerUin.toString()).json, + message = if(ShamrockConfig.useCQ()) raw.json + else elements.toSegments(record.chatType, record.peerUin.toString()).map { + it.toJson() + }.json, rawMessage = raw, font = 0, sender = Sender( diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/WebSocketClientService.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/WebSocketClientService.kt index 7fe00ab..4954da5 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/WebSocketClientService.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/WebSocketClientService.kt @@ -13,7 +13,7 @@ import moe.fuqiuluo.shamrock.remote.service.data.push.* import moe.fuqiuluo.shamrock.tools.json import moe.fuqiuluo.qqinterface.servlet.GroupSvc import moe.fuqiuluo.qqinterface.servlet.TicketSvc -import moe.fuqiuluo.qqinterface.servlet.msg.toSegment +import moe.fuqiuluo.qqinterface.servlet.msg.convert.toSegments internal class WebSocketClientService( override val address: String, @@ -288,10 +288,9 @@ internal class WebSocketClientService( targetId = if (msgType != MsgType.Private) 0 else record.peerUin, peerId = if (record.senderUin == uin) record.peerUin else uin, userId = record.senderUin, - message = if (ShamrockConfig.useCQ()) raw.json else elements.toSegment( - record.chatType, - record.peerUin.toString() - ).json, + message = if (ShamrockConfig.useCQ()) raw.json else elements.toSegments(record.chatType, record.peerUin.toString()).map { + it.toJson() + }.json, rawMessage = raw, font = 0, sender = Sender( diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/WebSocketService.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/WebSocketService.kt index 67b3081..9dd4d41 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/WebSocketService.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/WebSocketService.kt @@ -9,7 +9,7 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import moe.fuqiuluo.qqinterface.servlet.GroupSvc import moe.fuqiuluo.qqinterface.servlet.TicketSvc -import moe.fuqiuluo.qqinterface.servlet.msg.toSegment +import moe.fuqiuluo.qqinterface.servlet.msg.convert.toSegments import moe.fuqiuluo.shamrock.helper.ErrorTokenException import moe.fuqiuluo.shamrock.remote.service.api.WebSocketPushServlet import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig @@ -297,7 +297,9 @@ internal class WebSocketService(port: Int): WebSocketPushServlet(port) { targetId = if(msgType != MsgType.Private) 0 else record.peerUin, peerId = if (record.senderUin == uin) record.peerUin else uin, userId = record.senderUin, - message = if (ShamrockConfig.useCQ()) raw.json else elements.toSegment(record.chatType, record.peerUin.toString()).json, + message = if (ShamrockConfig.useCQ()) raw.json else elements.toSegments(record.chatType, record.peerUin.toString()).map { + it.toJson() + }.json, rawMessage = raw, font = 0, sender = Sender( diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/config/ShamrockConfig.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/config/ShamrockConfig.kt index 64867be..5019413 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/config/ShamrockConfig.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/config/ShamrockConfig.kt @@ -179,4 +179,9 @@ internal object ShamrockConfig { val mmkv = MMKVFetcher.mmkvWithId("shamrock_config") return mmkv.getInt("ssl_port", getPort()) } + + fun isDev(): Boolean { + val mmkv = MMKVFetcher.mmkvWithId("shamrock_config") + return mmkv.getBoolean("dev", false) + } } \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/Message.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/Message.kt index 145fad6..a01ae80 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/Message.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/Message.kt @@ -17,7 +17,7 @@ internal data class MessageDetail( @SerialName("message_id") val msgId: Int, @SerialName("real_id") val realId: Int, @SerialName("sender") val sender: MessageSender, - @SerialName("message") val message: ArrayList>, + @SerialName("message") val message: List>, @SerialName("group_id") val groupId: Long = 0, @SerialName("peer_id") val peerId: Long, @SerialName("target_id") val targetId: Long = 0, 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 e2a8406..f08227c 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 @@ -7,7 +7,7 @@ import com.tencent.qqnt.kernel.nativeinterface.* import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch -import moe.fuqiuluo.qqinterface.servlet.msg.toCQCode +import moe.fuqiuluo.qqinterface.servlet.msg.convert.toCQCode import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc import moe.fuqiuluo.shamrock.remote.service.api.GlobalPusher import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig