diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetTroopMemberList.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetTroopMemberList.kt index 6138cdf..8540d7b 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetTroopMemberList.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/GetTroopMemberList.kt @@ -53,7 +53,6 @@ internal object GetTroopMemberList : IActionHandler() { role = when { GroupSvc.getOwner(groupId) .toString() == info.memberuin -> MemberRole.Owner - info.memberuin.toLong() in GroupSvc.getAdminList(groupId) -> MemberRole.Admin else -> MemberRole.Member }, diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendForwardMsg.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendForwardMsg.kt new file mode 100644 index 0000000..33439ad --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendForwardMsg.kt @@ -0,0 +1,233 @@ +@file:OptIn(DelicateCoroutinesApi::class) + +package moe.fuqiuluo.shamrock.remote.action.handlers + +import com.tencent.qqnt.kernel.nativeinterface.MsgConstant +import com.tencent.qqnt.kernel.nativeinterface.MultiMsgInfo +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import moe.fuqiuluo.qqinterface.servlet.MsgSvc +import moe.fuqiuluo.shamrock.remote.action.ActionSession +import moe.fuqiuluo.shamrock.remote.action.IActionHandler +import moe.fuqiuluo.qqinterface.servlet.TicketSvc +import moe.fuqiuluo.qqinterface.servlet.msg.convert.toSegments +import moe.fuqiuluo.shamrock.helper.Level +import moe.fuqiuluo.shamrock.helper.LogCenter +import moe.fuqiuluo.shamrock.helper.MessageHelper +import moe.fuqiuluo.shamrock.tools.EmptyJsonObject +import moe.fuqiuluo.shamrock.tools.EmptyJsonString +import moe.fuqiuluo.shamrock.tools.asInt +import moe.fuqiuluo.shamrock.tools.asJsonObject +import moe.fuqiuluo.shamrock.tools.asString +import moe.fuqiuluo.shamrock.tools.asStringOrNull +import moe.fuqiuluo.shamrock.tools.json +import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** + * 合并转发消息节点数据类 + */ +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 SendPrivateForwardMsg: IActionHandler() { + override suspend fun internalHandle(session: ActionSession): String { + val groupId = session.getString("user_id") + if (session.isArray("messages")) { + val messages = session.getArray("messages") + return invoke(messages, groupId, session.echo) + } + return logic("未知格式合并转发消息", session.echo) + } + + suspend operator fun invoke( + message: JsonArray, + userId: String, + echo: JsonElement = EmptyJsonString + ): String { + kotlin.runCatching { + val kernelService = NTServiceFetcher.kernelService + val sessionService = kernelService.wrapperSession + val msgService = sessionService.msgService + val selfUin = TicketSvc.getUin() + + val msgs = message.map { + if (it.asJsonObject["type"].asStringOrNull != "node") return@map ForwardMsgNode.EmptyNode // 过滤非node类型消息段 + it.asJsonObject["data"].asJsonObject.let { data -> + if (data.containsKey("content")) + ForwardMsgNode.MessageNode( + name = data["name"].asStringOrNull ?: "", + content = data["content"] + ) + else ForwardMsgNode.MessageIdNode(data["id"].asInt) + } + }.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.sendMemberName + .ifBlank { record.sendNickName } + .ifBlank { record.sendRemarkName } + .ifBlank { record.peerName }, + content = record.toSegments().map { segment -> + segment.toJson() + }.json + ) + } + } else { + it as ForwardMsgNode.MessageNode + } + }.filter { + it.content != null + } + + val multiNodes = msgs.map { node -> + suspendCoroutine { + GlobalScope.launch { + var msgId: Long = 0 + msgId = MessageHelper.sendMessageWithMsgId(MsgConstant.KCHATTYPEC2C, selfUin, node.content!!.let { msg -> + if (msg is JsonArray) msg else MessageHelper.decodeCQCode(msg.asString) + }, { code, why -> + if (code != 0) { + LogCenter.log("合并转发消息节点消息发送失败:$code($why)", Level.WARN) + } + it.resume(node.name to msgId) + }).first + } + } + } + + val from = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, selfUin) + val to = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, userId) + msgService.multiForwardMsg(ArrayList().apply { + multiNodes.forEach { add(MultiMsgInfo(it.second, it.first)) } + }.also { it.reverse() }, from, to) { code, why -> + if (code != 0) + LogCenter.log("合并转发消息:$code($why)", Level.WARN) + } + return ok(data = EmptyJsonObject, echo = echo) + }.onFailure { + return error("error: $it", echo) + } + return logic("合并转发消息失败(unknown error)", echo) + } + + override val requiredParams: Array = arrayOf("user_id") + + override fun path(): String = "send_private_forward_msg" +} + +/** + * 群聊合并转发 + */ +internal object SendGroupForwardMsg: IActionHandler() { + override suspend fun internalHandle(session: ActionSession): String { + val groupId = session.getString("group_id") + if (session.isArray("messages")) { + val messages = session.getArray("messages") + return invoke(messages, groupId, session.echo) + } + return logic("未知格式合并转发消息", session.echo) + } + + suspend operator fun invoke( + message: JsonArray, + groupId: String, + echo: JsonElement = EmptyJsonString + ): String { + kotlin.runCatching { + val kernelService = NTServiceFetcher.kernelService + val sessionService = kernelService.wrapperSession + val msgService = sessionService.msgService + val selfUin = TicketSvc.getUin() + + val msgs = message.map { + if (it.asJsonObject["type"].asStringOrNull != "node") return@map ForwardMsgNode.EmptyNode // 过滤非node类型消息段 + it.asJsonObject["data"].asJsonObject.let { data -> + if (data.containsKey("content")) + ForwardMsgNode.MessageNode( + name = data["name"].asStringOrNull ?: "", + content = data["content"] + ) + else ForwardMsgNode.MessageIdNode(data["id"].asInt) + } + }.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.sendMemberName + .ifBlank { record.sendNickName } + .ifBlank { record.sendRemarkName } + .ifBlank { record.peerName }, + content = record.toSegments().map { segment -> + segment.toJson() + }.json + ) + } + } else { + it as ForwardMsgNode.MessageNode + } + }.filter { + it.content != null + } + + val multiNodes = msgs.map { node -> + suspendCoroutine { + GlobalScope.launch { + var msgId: Long = 0 + msgId = MessageHelper.sendMessageWithMsgId(MsgConstant.KCHATTYPEC2C, selfUin, node.content!!.let { msg -> + if (msg is JsonArray) msg else MessageHelper.decodeCQCode(msg.asString) + }, { code, why -> + if (code != 0) { + LogCenter.log("合并转发消息节点消息发送失败:$code($why)", Level.WARN) + } + it.resume(node.name to msgId) + }).first + } + } + } + + val from = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, selfUin) + val to = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, groupId) + msgService.multiForwardMsg(ArrayList().apply { + multiNodes.forEach { add(MultiMsgInfo(it.second, it.first)) } + }.also { it.reverse() }, from, to) { code, why -> + if (code != 0) + LogCenter.log("合并转发消息:$code($why)", Level.WARN) + } + return ok(data = EmptyJsonObject, echo = echo) + }.onFailure { + return error("error: $it", echo) + } + return logic("合并转发消息失败(unknown error)", echo) + } + + override val requiredParams: Array = arrayOf("group_id") + + override fun path(): String = "send_group_forward_msg" +} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendGroupForwardMsg.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendGroupForwardMsg.kt deleted file mode 100644 index 0a87768..0000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendGroupForwardMsg.kt +++ /dev/null @@ -1,125 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import com.tencent.qqnt.kernel.nativeinterface.MultiMsgInfo -import kotlinx.atomicfu.atomic -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.MsgSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.qqinterface.servlet.TicketSvc -import moe.fuqiuluo.qqinterface.servlet.msg.convert.toSegments -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.tools.EmptyJsonObject -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.tools.asInt -import moe.fuqiuluo.shamrock.tools.asJsonObject -import moe.fuqiuluo.shamrock.tools.asString -import moe.fuqiuluo.shamrock.tools.asStringOrNull -import moe.fuqiuluo.shamrock.tools.json -import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher - -internal object SendGroupForwardMsg: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getString("group_id") - if (session.isArray("messages")) { - val messages = session.getArray("messages") - return invoke(messages, groupId, session.echo) - } - return logic("未知格式合并转发消息", session.echo) - } - - suspend operator fun invoke( - message: JsonArray, - groupId: String, - echo: JsonElement = EmptyJsonString - ): String { - kotlin.runCatching { - val kernelService = NTServiceFetcher.kernelService - val sessionService = kernelService.wrapperSession - val msgService = sessionService.msgService - val selfUin = TicketSvc.getUin() - - val msgs = message.map { - it.asJsonObject["data"].asJsonObject.let { data -> - if (data.containsKey("content")) - MessageNode( - name = data["name"].asStringOrNull ?: "", - content = data["content"] - ) - else MessageIdNode(data["id"].asInt) - } - }.map { - if (it is MessageIdNode) { - val recordResult = MsgSvc.getMsg(it.id) - if (recordResult.isFailure) { - EmptyNode - } else { - val record = recordResult.getOrThrow() - MessageNode( - name = record.sendMemberName - .ifBlank { record.sendNickName } - .ifBlank { record.sendRemarkName } - .ifBlank { record.peerName }, - content = record.toSegments().map { segment -> - segment.toJson() - }.json - ) - } - } else { - it as MessageNode - } - }.filter { - it.content != null - } - - lateinit var forwardMsgCallback: (() -> Unit) - val availableMsgSize = atomic(0) - val msgIds = msgs.map { - it.name to MessageHelper.sendMessageWithMsgId(MsgConstant.KCHATTYPEC2C, selfUin, it.content!!.let { msg -> - if (msg is JsonArray) msg else MessageHelper.decodeCQCode(msg.asString) - }, { code, why -> - if (code != 0) { - availableMsgSize.incrementAndGet() - LogCenter.log("合并转发消息节点消息发送失败:$code($why)", Level.WARN) - } - if (availableMsgSize.incrementAndGet() == msgs.size) { - forwardMsgCallback.invoke() - } - }).first - } - - val from = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, selfUin) - val to = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, groupId) - forwardMsgCallback = { - msgService.multiForwardMsg(ArrayList().apply { - msgIds.forEach { add(MultiMsgInfo(it.second, it.first)) } - }.also { it.reverse() }, from, to) { code, why -> - if (code != 0) - LogCenter.log("合并转发消息:$code($why)", Level.WARN) - } - } - return ok(data = EmptyJsonObject, echo = echo) - }.onFailure { - return error("error: $it", echo) - } - return logic("合并转发消息失败(unknown error)", echo) - } - - override val requiredParams: Array = arrayOf("group_id") - - override fun path(): String = "send_group_forward_msg" - - class MessageIdNode( - val id: Int - ): Node - open class MessageNode( - val name: String, - val content: JsonElement? - ): Node - object EmptyNode: MessageNode("", null) - interface Node -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendPrivateForwardMsg.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendPrivateForwardMsg.kt deleted file mode 100644 index d641918..0000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendPrivateForwardMsg.kt +++ /dev/null @@ -1,125 +0,0 @@ -package moe.fuqiuluo.shamrock.remote.action.handlers - -import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import com.tencent.qqnt.kernel.nativeinterface.MultiMsgInfo -import kotlinx.atomicfu.atomic -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonElement -import moe.fuqiuluo.qqinterface.servlet.MsgSvc -import moe.fuqiuluo.shamrock.remote.action.ActionSession -import moe.fuqiuluo.shamrock.remote.action.IActionHandler -import moe.fuqiuluo.qqinterface.servlet.TicketSvc -import moe.fuqiuluo.qqinterface.servlet.msg.convert.toSegments -import moe.fuqiuluo.shamrock.helper.Level -import moe.fuqiuluo.shamrock.helper.LogCenter -import moe.fuqiuluo.shamrock.helper.MessageHelper -import moe.fuqiuluo.shamrock.tools.EmptyJsonObject -import moe.fuqiuluo.shamrock.tools.EmptyJsonString -import moe.fuqiuluo.shamrock.tools.asInt -import moe.fuqiuluo.shamrock.tools.asJsonObject -import moe.fuqiuluo.shamrock.tools.asString -import moe.fuqiuluo.shamrock.tools.asStringOrNull -import moe.fuqiuluo.shamrock.tools.json -import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher - -internal object SendPrivateForwardMsg: IActionHandler() { - override suspend fun internalHandle(session: ActionSession): String { - val groupId = session.getString("user_id") - if (session.isArray("messages")) { - val messages = session.getArray("messages") - return invoke(messages, groupId, session.echo) - } - return logic("未知格式合并转发消息", session.echo) - } - - suspend operator fun invoke( - message: JsonArray, - userId: String, - echo: JsonElement = EmptyJsonString - ): String { - kotlin.runCatching { - val kernelService = NTServiceFetcher.kernelService - val sessionService = kernelService.wrapperSession - val msgService = sessionService.msgService - val selfUin = TicketSvc.getUin() - - val msgs = message.map { - it.asJsonObject["data"].asJsonObject.let { data -> - if (data.containsKey("content")) - MessageNode( - name = data["name"].asStringOrNull ?: "", - content = data["content"] - ) - else MessageIdNode(data["id"].asInt) - } - }.map { - if (it is MessageIdNode) { - val recordResult = MsgSvc.getMsg(it.id) - if (recordResult.isFailure) { - EmptyNode - } else { - val record = recordResult.getOrThrow() - MessageNode( - name = record.sendMemberName - .ifBlank { record.sendNickName } - .ifBlank { record.sendRemarkName } - .ifBlank { record.peerName }, - content = record.toSegments().map { segment -> - segment.toJson() - }.json - ) - } - } else { - it as MessageNode - } - }.filter { - it.content != null - } - - lateinit var forwardMsgCallback: (() -> Unit) - val availableMsgSize = atomic(0) - val msgIds = msgs.map { - it.name to MessageHelper.sendMessageWithMsgId(MsgConstant.KCHATTYPEC2C, selfUin, it.content!!.let { msg -> - if (msg is JsonArray) msg else MessageHelper.decodeCQCode(msg.asString) - }, { code, why -> - if (code != 0) { - availableMsgSize.incrementAndGet() - LogCenter.log("合并转发消息节点消息发送失败:$code($why)", Level.WARN) - } - if (availableMsgSize.incrementAndGet() == msgs.size) { - forwardMsgCallback.invoke() - } - }).first - } - - val from = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, selfUin) - val to = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, userId) - forwardMsgCallback = { - msgService.multiForwardMsg(ArrayList().apply { - msgIds.forEach { add(MultiMsgInfo(it.second, it.first)) } - }.also { it.reverse() }, from, to) { code, why -> - if (code != 0) - LogCenter.log("合并转发消息:$code($why)", Level.WARN) - } - } - return ok(data = EmptyJsonObject, echo = echo) - }.onFailure { - return error("error: $it", echo) - } - return logic("合并转发消息失败(unknown error)", echo) - } - - override val requiredParams: Array = arrayOf("user_id") - - override fun path(): String = "send_private_forward_msg" - - class MessageIdNode( - val id: Int - ): Node - open class MessageNode( - val name: String, - val content: JsonElement? - ): Node - object EmptyNode: MessageNode("", null) - interface Node -} \ 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 93f859f..d1926ed 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 @@ -11,6 +11,7 @@ import io.ktor.server.routing.post import io.ktor.server.routing.route import moe.fuqiuluo.shamrock.helper.db.MessageDB import moe.fuqiuluo.shamrock.remote.action.handlers.* +import moe.fuqiuluo.shamrock.remote.entries.Status import moe.fuqiuluo.shamrock.tools.fetchGetOrNull import moe.fuqiuluo.shamrock.tools.fetchGetOrThrow import moe.fuqiuluo.shamrock.tools.fetchOrNull @@ -22,12 +23,21 @@ import moe.fuqiuluo.shamrock.tools.fetchPostOrThrow import moe.fuqiuluo.shamrock.tools.getOrPost import moe.fuqiuluo.shamrock.tools.isJsonData import moe.fuqiuluo.shamrock.tools.isJsonString +import moe.fuqiuluo.shamrock.tools.respond fun Routing.messageAction() { + route("/send_group_forward_msg") { + post { + val groupId = fetchPostOrNull("group_id") + val messages = fetchPostJsonArray("messages") + call.respondText(SendGroupForwardMsg(messages, groupId ?: ""), ContentType.Application.Json) + } + get { + respond(false, Status.InternalHandlerError, "Not support GET method") + } + } post("/send_group_forward_msg") { - val groupId = fetchPostOrNull("group_id") - val messages = fetchPostJsonArray("messages") - call.respondText(SendGroupForwardMsg(messages, groupId ?: ""), ContentType.Application.Json) + } post("/send_private_forward_msg") {