From fa6634d6afc17b062afe023149fa0bd1f36b2b74 Mon Sep 17 00:00:00 2001 From: Simplxs Date: Sat, 2 Dec 2023 22:59:37 +0800 Subject: [PATCH] `Shamrock`: fix #111 and add group sign event --- .../qqinterface/servlet/msg/MessageMaker.kt | 14 +- .../msg/convert/MessageElemConverter.kt | 2 +- .../action/handlers/SendForwardMessage.kt | 139 ++++++++++-------- .../shamrock/remote/api/MessageAction.kt | 16 +- .../service/api/GlobalEventTransmitter.kt | 19 +++ .../remote/service/data/push/NoticeEvent.kt | 12 ++ .../service/listener/PrimitiveListener.kt | 55 ++++--- 7 files changed, 168 insertions(+), 89 deletions(-) diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/MessageMaker.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/MessageMaker.kt index c40d84e..ad9f9e0 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/MessageMaker.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/MessageMaker.kt @@ -627,10 +627,16 @@ internal object MessageMaker { else -> { val info = GroupSvc.getTroopMemberInfoByUin(peerId, qq, true).onFailure { LogCenter.log("无法获取群成员信息: $qq", Level.ERROR) - }.getOrThrow() - at.content = "@${info.troopnick - .ifNullOrEmpty(info.friendnick) - .ifNullOrEmpty(qq)}" + }.getOrNull() + if (info != null) { + at.content = "@${ + info.troopnick + .ifNullOrEmpty(info.friendnick) + .ifNullOrEmpty(qq) + }" + } else { + at.content = "@${data["name"].asStringOrNull.ifNullOrEmpty(qq)}" + } at.atType = MsgConstant.ATTYPEONE at.atNtUid = ContactHelper.getUidByUinAsync(qq.toLong()) } 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 index bac4567..602a6f4 100644 --- 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 @@ -331,7 +331,7 @@ internal sealed class MessageElemConverter: IMessageConvert { MsgConstant.GRAYTIPELEMENTSUBTYPEXMLMSG -> { val notify = tip.xmlElement when(notify.busiId) { - /* 群戳一戳 */1061L -> {} + /* 群戳一戳 */1061L, /* 群打卡 */1068L -> {} else -> LogCenter.log("不支持的灰条类型(XML): ${notify.busiId}", Level.WARN) } } 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 6a67c4f..8e8583f 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 @@ -2,9 +2,7 @@ package moe.fuqiuluo.shamrock.remote.action.handlers import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import com.tencent.qqnt.kernel.nativeinterface.MultiMsgInfo -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.* import moe.fuqiuluo.qqinterface.servlet.MsgSvc import moe.fuqiuluo.qqinterface.servlet.TicketSvc import moe.fuqiuluo.qqinterface.servlet.msg.convert.toSegments @@ -18,19 +16,6 @@ import moe.fuqiuluo.shamrock.remote.service.data.ForwardMessageResult import moe.fuqiuluo.shamrock.tools.* import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher -sealed interface ForwardMsgNode { - class MessageIdNode( - val id: Int - ) : ForwardMsgNode - - open class MessageNode( - val name: String, - val content: JsonElement? - ) : ForwardMsgNode - - object EmptyNode : MessageNode("", null) -} - internal object SendForwardMessage : IActionHandler() { override suspend fun internalHandle(session: ActionSession): String { val detailType = session.getStringOrNull("detail_type") ?: session.getStringOrNull("message_type") @@ -74,7 +59,7 @@ internal object SendForwardMessage : IActionHandler() { suspend operator fun invoke( chatType: Int, peerId: String, - message: JsonArray, + messages: JsonArray, echo: JsonElement = EmptyJsonString ): String { kotlin.runCatching { @@ -83,63 +68,91 @@ internal object SendForwardMessage : IActionHandler() { val msgService = sessionService.msgService val selfUin = TicketSvc.getUin() - val nodes = message.map { - if (it.asJsonObject["type"].asStringOrNull != "node") return@map ForwardMsgNode.EmptyNode // 过滤非node类型消息段 - it.asJsonObject["data"].asJsonObject.let { data -> - if (data.containsKey("content")) { - if (data["content"] is JsonArray) { - data["content"].asJsonArray.forEach { msg -> - if (msg.asJsonObject["type"].asStringOrNull == "node") { - LogCenter.log("合并转发消息不支持嵌套", Level.WARN) - return@map ForwardMsgNode.EmptyNode - } - } - } - ForwardMsgNode.MessageNode( - name = data["name"].asStringOrNull ?: "", - content = data["content"] - ) - } else ForwardMsgNode.MessageIdNode(data["id"].asInt) + val multiNodes = messages.map { + if (it.asJsonObject["type"].asStringOrNull != "node") { + LogCenter.log("包含非node类型节点", Level.WARN) + return@map null } - }.map { - if (it is ForwardMsgNode.MessageIdNode) { - val recordResult = MsgSvc.getMsg(it.id) - if (!recordResult.isFailure) { - ForwardMsgNode.EmptyNode - } else { - val record = recordResult.getOrThrow() - ForwardMsgNode.MessageNode( - name = record.peerName, - content = record.toSegments().map { segment -> + if (it.asJsonObject["data"] !is JsonObject) { + LogCenter.log("data字段错误", Level.WARN) + return@map null + } + it.asJsonObject["data"].asJsonObject.let { data -> + if (data.containsKey("id")) { + val record = MsgSvc.getMsg(data["id"].asInt).getOrNull() + if (record == null) { + LogCenter.log("合并转发消息节点消息获取失败:${data["id"]}", Level.WARN) + return@map null + } else { + record.peerName to record.toSegments().map { segment -> segment.toJson() }.json - ) + } + } else if (data.containsKey("content")) { + (data["name"].asStringOrNull ?: "Anno") to when (val raw = data["content"]) { + is JsonObject -> raw.asJsonArray + is JsonArray -> raw.asJsonArray + else -> MessageHelper.decodeCQCode(raw.asString) + } + } else { + LogCenter.log("消息节点缺少id或content字段", Level.WARN) + return@map null } - } else { - it as ForwardMsgNode.MessageNode - } - }.filter { - it.content != null - }.map { node -> - val result = MessageHelper.sendMessageNoCb(MsgConstant.KCHATTYPEC2C, selfUin, node.content.let { msg -> - when (msg) { - is JsonArray -> msg - is JsonObject -> listOf(msg).jsonArray - else -> MessageHelper.decodeCQCode(msg.asString) + }.let { node -> + val content = node.second.map { msg -> + when (msg.asJsonObject["type"].asStringOrNull ?: "text") { + "at" -> { + buildJsonObject { + put("type", "text") + putJsonObject("data") { + put( + "text", "@${ + msg.asJsonObject["data"].asJsonObject["name"].asStringOrNull.ifNullOrEmpty( + msg.asJsonObject["data"].asJsonObject["qq"].asString + ) + }" + ) + } + } + } + + "voice" -> { + buildJsonObject { + put("type", "text") + putJsonObject("data") { + put("text", "[语音]") + } + } + } + + "node" -> { + LogCenter.log("合并转发消息暂时不支持嵌套", Level.WARN) + buildJsonObject { + put("type", "text") + putJsonObject("data") { + put("text", "[合并转发消息]") + } + } + } + + else -> msg + } + }.json + + val result = MessageHelper.sendMessageNoCb(MsgConstant.KCHATTYPEC2C, selfUin, content) + if (result.first != 0) { + LogCenter.log("合并转发消息节点消息发送失败", Level.WARN) } - }) - if (result.first != 0) { - LogCenter.log("合并转发消息节点消息发送失败", Level.WARN) + result.second to node.first } - return@map result.second - } + }.filterNotNull() val from = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, selfUin) val to = MessageHelper.generateContact(chatType, peerId) val uniseq = MessageHelper.generateMsgId(chatType) msgService.multiForwardMsg(ArrayList().apply { - nodes.forEach { add(MultiMsgInfo(it, "Anno")) } + multiNodes.forEach { add(MultiMsgInfo(it.first, it.second)) } }.also { it.reverse() }, from, to, MsgSvc.MessageCallback(peerId, uniseq.first)) return ok( @@ -154,7 +167,7 @@ internal object SendForwardMessage : IActionHandler() { return logic("合并转发消息失败(unknown error)", echo) } - override val requiredParams: Array = arrayOf("message") + override val requiredParams: Array = arrayOf("messages") override fun path(): String = "send_forward_msg" } \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/MessageAction.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/MessageAction.kt index 0e32a18..5c038c4 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/MessageAction.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/MessageAction.kt @@ -29,7 +29,7 @@ import moe.fuqiuluo.shamrock.tools.jsonArray import moe.fuqiuluo.shamrock.tools.respond fun Routing.messageAction() { - route("/send_group_forward_msg") { + route("/send_group_forward_(msg|message)".toRegex()) { post { val groupId = fetchPostOrNull("group_id") val messages = fetchPostJsonArray("messages") @@ -40,7 +40,7 @@ fun Routing.messageAction() { } } - route("/send_private_forward_msg") { + route("/send_private_forward_(msg|message)".toRegex()) { post { val userId = fetchPostOrNull("user_id") val messages = fetchPostJsonArray("messages") @@ -51,6 +51,18 @@ fun Routing.messageAction() { } } + route("/send_forward_(msg|message)".toRegex()) { + post { + val userId = fetchPostOrNull("user_id") + val groupId = fetchPostOrNull("group_id") + val messages = fetchPostJsonArray("messages") + call.respondText(SendForwardMessage(MsgConstant.KCHATTYPEC2C, userId ?: groupId?: "", messages), ContentType.Application.Json) + } + get { + respond(false, Status.InternalHandlerError, "Not support GET method") + } + } + getOrPost("/get_forward_msg") { val id = fetchOrThrow("id") call.respondText(GetForwardMsg(id), ContentType.Application.Json) 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 938de43..6e40334 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 @@ -25,6 +25,7 @@ import moe.fuqiuluo.shamrock.remote.service.data.push.RequestEvent import moe.fuqiuluo.shamrock.remote.service.data.push.RequestSubType import moe.fuqiuluo.shamrock.remote.service.data.push.RequestType import moe.fuqiuluo.shamrock.remote.service.data.push.Sender +import moe.fuqiuluo.shamrock.remote.service.data.push.SignDetail import moe.fuqiuluo.shamrock.tools.ShamrockDsl import moe.fuqiuluo.shamrock.tools.json import java.util.ArrayList @@ -222,6 +223,24 @@ internal object GlobalEventTransmitter: BaseSvc() { * 群聊通知 通知器 */ object GroupNoticeTransmitter { + suspend fun transGroupSign(time: Long, target: Long, action: String?, rankImg: String?, groupCode: Long): Boolean { + pushNotice(NoticeEvent( + time = time, + selfId = app.longAccountUin, + postType = PostType.Notice, + type = NoticeType.Notify, + subType = NoticeSubType.Sign, + userId = target, + groupId = groupCode, + target = target, + signDetail = SignDetail( + rankImg = rankImg, + action = action + ) + )) + return true + } + suspend fun transGroupPoke(time: Long, operation: Long, target: Long, action: String?, suffix: String?, actionImg: String?, groupCode: Long): Boolean { pushNotice(NoticeEvent( time = time, diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/push/NoticeEvent.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/push/NoticeEvent.kt index 7b75a5c..f7ac137 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/push/NoticeEvent.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/data/push/NoticeEvent.kt @@ -42,6 +42,8 @@ internal enum class NoticeSubType { @SerialName("kick_me") KickMe, @SerialName("poke") Poke, + @SerialName("sign") Sign, + @SerialName("title") Title, @SerialName("delete") Delete, @@ -87,6 +89,9 @@ internal data class NoticeEvent( // 戳一戳 @SerialName("poke_detail") val pokeDetail: PokeDetail? = null, + // 群打卡 + @SerialName("sign_detail") val signDetail: SignDetail? = null, + ) /** @@ -131,4 +136,11 @@ internal data class PokeDetail ( val suffix: String? = "", @SerialName("action_img_url") val actionImg: String? = "https://tianquan.gtimg.cn/nudgeaction/item/0/expression.jpg", +) + +@Serializable +internal data class SignDetail ( + val action: String? = "今日第1个打卡", + @SerialName("rank_img") + val rankImg: String? = "", ) \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/PrimitiveListener.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/PrimitiveListener.kt index 7dc29ae..e40b9ed 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/PrimitiveListener.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/service/listener/PrimitiveListener.kt @@ -82,7 +82,7 @@ internal object PrimitiveListener { 12 -> onGroupBan(msgTime, pb) 16 -> onGroupTitleChange(msgTime, pb) 17 -> onGroupRecall(msgTime, pb) - 20 -> onGroupPoke(msgTime, pb) + 20 -> onGroupPokeAndGroupSign(msgTime, pb) 21 -> onEssenceMessage(msgTime, pb) } } @@ -188,6 +188,7 @@ internal object PrimitiveListener { readPacket.discardExact(1) ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt())) } else pb[1, 3, 2] + readPacket.release() val targetUin = detail[5, 5].asLong @@ -219,6 +220,7 @@ internal object PrimitiveListener { readPacket.discardExact(1) ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt())) } else pb[1, 3, 2] + readPacket.release() val groupId = detail[4].asLong val mesSeq = detail[37].asInt @@ -254,24 +256,14 @@ internal object PrimitiveListener { } - private suspend fun onGroupPoke(time: Long, pb: ProtoMap) { - val groupCode1 = pb[1, 1, 1].asULong - - var groupCode: Long = groupCode1 + private suspend fun onGroupPokeAndGroupSign(time: Long, pb: ProtoMap) { + val groupCode = pb[1, 1, 1].asULong val readPacket = ByteReadPacket(pb[1, 3, 2].asByteArray) - val groupCode2 = readPacket.readBuf32Long() - - var detail = if (groupCode2 == groupCode1) { - groupCode = groupCode2 + val detail = if (readPacket.readBuf32Long() == groupCode) { readPacket.discardExact(1) ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt())) } else pb[1, 3, 2] - if (detail !is ProtoMap) { - groupCode = groupCode2 - readPacket.discardExact(1) - detail = ProtoUtils.decodeFromByteArray(readPacket.readBytes(readPacket.readShort().toInt())) - } readPacket.release() lateinit var target: String @@ -279,6 +271,7 @@ internal object PrimitiveListener { var action: String? = null var suffix: String? = null var actionImg: String? = null + var rankImg: String? = null detail[26][7] .asList .value @@ -287,18 +280,42 @@ internal object PrimitiveListener { when (it[1].asUtf8String) { "uin_str1" -> operation = value "uin_str2" -> target = value + // "nick_str1" -> operation_nick = value + // "nick_str2" -> operation_nick = value "action_str" -> action = value "alt_str1" -> action = value "suffix_str" -> suffix = value "action_img_url" -> actionImg = value + + "mqq_uin" -> target = value + // "mqq_nick" -> operation_nick = value + "user_sign" -> action = value + "rank_img" -> rankImg = value + // "sign_word" -> 我也要打卡 + } + } + when (detail[26][2].asInt) { + 1061 -> { + LogCenter.log("群戳一戳($groupCode): $operation $action $target $suffix") + if (!GlobalEventTransmitter.GroupNoticeTransmitter + .transGroupPoke(time, operation.toLong(), target.toLong(), action, suffix, actionImg, groupCode) + ) { + LogCenter.log("群戳一戳推送失败!", Level.WARN) } } - LogCenter.log("群戳一戳($groupCode): $operation $action $target $suffix") - if (!GlobalEventTransmitter.GroupNoticeTransmitter - .transGroupPoke(time, operation.toLong(), target.toLong(), action, suffix, actionImg, groupCode) - ) { - LogCenter.log("群戳一戳推送失败!", Level.WARN) + 1068 -> { + LogCenter.log("群打卡($groupCode): $action $target") + if (!GlobalEventTransmitter.GroupNoticeTransmitter + .transGroupSign(time, target.toLong(), action, rankImg, groupCode) + ) { + LogCenter.log("群打卡推送失败!", Level.WARN) + } + } + + else -> { + LogCenter.log("onGroupPokeAndGroupSign unknown type ${detail[2].asInt}", Level.WARN) + } } }