From 72c3c7bdf7dc1c7405ff318a0d4fb0a5b9219a1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E6=B1=A0?= <98259561+whitechi73@users.noreply.github.com> Date: Tue, 30 Jan 2024 18:23:48 +0800 Subject: [PATCH] `Shamrock`: Rewrite timeout message sending --- .../fuqiuluo/qqinterface/servlet/MsgSvc.kt | 17 +-- .../fuqiuluo/shamrock/helper/MessageHelper.kt | 102 +++++++++++------- .../action/handlers/SendForwardMessage.kt | 9 +- .../remote/action/handlers/UploadGroupFile.kt | 6 +- .../action/handlers/UploadPrivateFile.kt | 6 +- .../remote/structures/SendMsgResult.kt | 8 ++ 6 files changed, 93 insertions(+), 55 deletions(-) create mode 100644 xposed/src/main/java/moe/fuqiuluo/shamrock/remote/structures/SendMsgResult.kt 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 0eedeaa..7c0e335 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/MsgSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/MsgSvc.kt @@ -23,6 +23,7 @@ import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.MessageHelper import moe.fuqiuluo.shamrock.helper.SendMsgException +import moe.fuqiuluo.shamrock.remote.structures.SendMsgResult import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher import moe.fuqiuluo.shamrock.xposed.helper.msgService @@ -170,8 +171,7 @@ internal object MsgSvc: BaseSvc() { peedId: String, message: JsonArray, fromId: String = peedId, - retryCnt: Int = 3 - ): Result> { + ): Result { // 主动临时消息 when (chatType) { MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> { @@ -181,14 +181,17 @@ internal object MsgSvc: BaseSvc() { } } } - val result = MessageHelper.sendMessageWithoutMsgId(chatType, peedId, message, fromId, MessageCallback(peedId, 0)) - return if (result.isFailure - && result.exceptionOrNull()?.javaClass == SendMsgException::class.java - && retryCnt > 0) { + val result = MessageHelper.sendMessageWithoutMsgId(chatType, peedId, message, fromId, MessageCallback(peedId, 0)) + result.onFailure { + LogCenter.log(it.stackTraceToString(), Level.ERROR) + return result + } + val sendResult = result.getOrThrow() + return if (sendResult.isTimeout) { // 发送失败,可能网络问题出现红色感叹号,重试 // 例如 rich media transfer failed delay(100) - sendToAio(chatType, peedId, message, fromId, retryCnt - 1) + MessageHelper.resendMsg(chatType, peedId, fromId, sendResult.qqMsgId, 3, sendResult.msgHashId) } else { result } 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 9fae405..2b65c8a 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/MessageHelper.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/MessageHelper.kt @@ -19,6 +19,7 @@ import moe.fuqiuluo.qqinterface.servlet.MsgSvc import moe.fuqiuluo.qqinterface.servlet.msg.MessageMaker 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 @@ -36,13 +37,13 @@ internal object MessageHelper { message: String, callback: IOperateCallback, fromId: String = peerId - ): Pair { + ): SendMsgResult { val uniseq = generateMsgId(chatType) - val msg = messageArrayToMessageElements(chatType, uniseq.second, peerId, decodeCQCode(message)).also { + val msg = messageArrayToMessageElements(chatType, uniseq.qqMsgId, peerId, decodeCQCode(message)).also { if (it.second.isEmpty() && !it.first) { error("消息合成失败,请查看日志或者检查输入。") } else if (it.second.isEmpty()) { - return System.currentTimeMillis() to 0 + return uniseq.copy(msgHashId = 0, msgTime = System.currentTimeMillis()) } }.second.filter { it.elementType != -1 @@ -50,6 +51,28 @@ 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 { + val contact = generateContact(chatType, peerId, fromId) + return resendMsg(contact, msgId, retryCnt, msgHashId) + } + + suspend fun resendMsg(contact: Contact, msgId: Long, retryCnt: Int, msgHashId: Int): Result { + 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) { + resendMsg(contact, msgId, retryCnt - 1, msgHashId) + } else { + Result.success(SendMsgResult(msgHashId, msgId, System.currentTimeMillis())) + } + } + return result ?: resendMsg(contact, msgId, retryCnt - 1, msgHashId) + } + @OptIn(DelicateCoroutinesApi::class) suspend fun sendMessageWithoutMsgId( chatType: Int, @@ -57,9 +80,9 @@ internal object MessageHelper { message: JsonArray, fromId: String = peerId, callback: IOperateCallback - ): Result> { + ): Result { val uniseq = generateMsgId(chatType) - val msg = messageArrayToMessageElements(chatType, uniseq.second, peerId, message).also { + val msg = messageArrayToMessageElements(chatType, uniseq.qqMsgId, peerId, message).also { if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。") }.second.filter { it.elementType != -1 @@ -67,7 +90,7 @@ internal object MessageHelper { // ActionMsg No Care if (msg.isEmpty()) { - return Result.success(System.currentTimeMillis() to 0) + return Result.success(uniseq.copy(msgTime = System.currentTimeMillis(), msgHashId = 0)) } val totalSize = msg.filter { @@ -78,13 +101,13 @@ internal object MessageHelper { (it.picElement?.fileSize ?: 0) + (it.pttElement?.fileSize ?: 0) + (it.videoElement?.fileSize ?: 0) }.reduceOrNull { a, b -> a + b } ?: 0 - val estimateTime = (totalSize / (300 * 1024)) * 1000 + 2000 + val estimateTime = (totalSize / (300 * 1024)) * 1000 + 2000 - lateinit var sendResultPair: Pair + lateinit var sendResult: SendMsgResult // msgTime to msgHash val sendRet = withTimeoutOrNull>(estimateTime) { suspendCancellableCoroutine { GlobalScope.launch { - sendResultPair = sendMessageWithoutMsgId( + sendResult = sendMessageWithoutMsgId( chatType, peerId, msg, @@ -96,10 +119,12 @@ internal object MessageHelper { } } } + if (sendRet?.first != 0) { - return Result.failure(SendMsgException(sendRet?.second ?: "发送消息超时")) + //return Result.failure(SendMsgException(sendRet?.second ?: "发送消息超时")) + return Result.success(uniseq.copy(isTimeout = true)) } - return Result.success(sendResultPair) + return Result.success(sendResult) } suspend fun sendMessageWithoutMsgId( @@ -108,7 +133,7 @@ internal object MessageHelper { message: ArrayList, fromId: String = peerId, callback: IOperateCallback - ): Pair { + ): SendMsgResult { return sendMessageWithoutMsgId(generateContact(chatType, peerId, fromId), message, callback) } @@ -116,24 +141,25 @@ internal object MessageHelper { contact: Contact, message: ArrayList, callback: IOperateCallback - ): Pair { + ): SendMsgResult { val uniseq = generateMsgId(contact.chatType) val nonMsg: Boolean = message.isEmpty() return if (!nonMsg) { val service = QRoute.api(IMsgService::class.java) if (callback is MsgSvc.MessageCallback) { - callback.msgHash = uniseq.first + callback.msgHash = uniseq.msgHashId } service.sendMsg( contact, - uniseq.second, + uniseq.qqMsgId, message, callback ) - System.currentTimeMillis() to uniseq.first + + uniseq.copy(msgTime = System.currentTimeMillis()) } else { - System.currentTimeMillis() to 0 + uniseq.copy(msgHashId = 0, msgTime = System.currentTimeMillis()) } } @@ -143,9 +169,9 @@ internal object MessageHelper { message: JsonArray, callback: IOperateCallback, fromId: String = peerId - ): Pair { + ): SendMsgResult { val uniseq = generateMsgId(chatType) - val msg = messageArrayToMessageElements(chatType, uniseq.second, peerId, message).also { + val msg = messageArrayToMessageElements(chatType, uniseq.qqMsgId, peerId, message).also { if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。") }.second.filter { it.elementType != -1 @@ -155,18 +181,18 @@ internal object MessageHelper { return if (!nonMsg) { val service = QRoute.api(IMsgService::class.java) if (callback is MsgSvc.MessageCallback) { - callback.msgHash = uniseq.first + callback.msgHash = uniseq.msgHashId } service.sendMsg( contact, - uniseq.second, + uniseq.qqMsgId, msg, callback ) - uniseq.second to uniseq.first + uniseq.copy(msgTime = System.currentTimeMillis()) } else { - uniseq.second to 0 + uniseq.copy(msgHashId = 0, msgTime = System.currentTimeMillis()) } } @@ -174,24 +200,25 @@ internal object MessageHelper { contact: Contact, message: ArrayList, callback: IOperateCallback - ): Pair { + ): SendMsgResult { val uniseq = generateMsgId(contact.chatType) val nonMsg: Boolean = message.isEmpty() return if (!nonMsg) { val service = QRoute.api(IMsgService::class.java) if (callback is MsgSvc.MessageCallback) { - callback.msgHash = uniseq.first + callback.msgHash = uniseq.msgHashId } service.sendMsg( contact, - uniseq.second, + uniseq.qqMsgId, message, callback ) - uniseq.second to uniseq.first + + uniseq.copy(msgTime = System.currentTimeMillis()) } else { - 0L to 0 + uniseq.copy(msgTime = 0, msgHashId = 0) } } @@ -200,24 +227,23 @@ internal object MessageHelper { peerId: String, message: JsonArray, fromId: String = peerId - ): Pair { + ): SendMsgResult { val uniseq = generateMsgId(chatType) - val msg = messageArrayToMessageElements(chatType, uniseq.second, peerId, message).also { + val msg = messageArrayToMessageElements(chatType, uniseq.qqMsgId, peerId, message).also { if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。") }.second.filter { it.elementType != -1 } as ArrayList val contact = generateContact(chatType, peerId, fromId) - val nonMsg: Boolean = message.isEmpty() - return if (!nonMsg) { + return if (!message.isEmpty()) { val service = QRoute.api(IMsgService::class.java) - return suspendCoroutine { - service.sendMsg(contact, uniseq.second, msg) { code, why -> - it.resume(code to uniseq.second) + return suspendCancellableCoroutine { + service.sendMsg(contact, uniseq.qqMsgId, msg) { code, why -> + it.resume(uniseq.copy(msgTime = System.currentTimeMillis())) } } } else { - -1 to uniseq.second + uniseq.copy(msgHashId = 0, msgTime = 0) } } @@ -287,10 +313,10 @@ internal object MessageHelper { return abs(key.hashCode()) } - fun generateMsgId(chatType: Int): Pair { + fun generateMsgId(chatType: Int): SendMsgResult { val msgId = createMessageUniseq(chatType, System.currentTimeMillis()) val hashCode: Int = generateMsgIdHash(chatType, msgId) - return hashCode to msgId + return SendMsgResult(hashCode, msgId, 0) } fun getMsgMappingByHash(hash: Int): MessageMapping? { 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 4afcef0..9ba85b8 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 @@ -145,10 +145,11 @@ internal object SendForwardMessage : IActionHandler() { }.json val result = MessageHelper.sendMessageNoCb(MsgConstant.KCHATTYPEC2C, selfUin, content) - if (result.first != 0) { + if (result.qqMsgId == 0L) { LogCenter.log("合并转发消息节点消息发送失败", Level.WARN) + return@map null } - result.second to node.first + result.qqMsgId to node.first } }.filterNotNull() @@ -158,11 +159,11 @@ internal object SendForwardMessage : IActionHandler() { val uniseq = MessageHelper.generateMsgId(chatType) msgService.multiForwardMsg(ArrayList().apply { multiNodes.forEach { add(MultiMsgInfo(it.first, it.second)) } - }.also { it.reverse() }, from, to, MsgSvc.MessageCallback(peerId, uniseq.first)) + }.also { it.reverse() }, from, to, MsgSvc.MessageCallback(peerId, uniseq.msgHashId)) return ok( ForwardMessageResult( - msgId = uniseq.first, + msgId = uniseq.msgHashId, forwardId = "" ), echo = echo ) diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadGroupFile.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadGroupFile.kt index 43970cd..a9bf3fa 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadGroupFile.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadGroupFile.kt @@ -108,12 +108,12 @@ internal object UploadGroupFile : IActionHandler() { val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, groupId) suspendCancellableCoroutine { msgService.sendMsgWithMsgId( - contact, msgIdPair.second, arrayListOf(msgElement) + contact, msgIdPair.qqMsgId, arrayListOf(msgElement) ) { code, reason -> LogCenter.log("群文件消息发送异常(code = $code, reason = $reason)") it.resume(null) } - RichMediaUploadHandler.registerListener(msgIdPair.second) { + RichMediaUploadHandler.registerListener(msgIdPair.qqMsgId) { it.resume(this) return@registerListener true } @@ -125,7 +125,7 @@ internal object UploadGroupFile : IActionHandler() { }.commonFileInfo return ok(data = FileUploadResult( - msgHash = msgIdPair.first, + msgHash = msgIdPair.msgHashId, bizid = info.bizType ?: 0, md5 = info.md5, sha = info.sha, diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadPrivateFile.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadPrivateFile.kt index 3939db8..f0b967f 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadPrivateFile.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadPrivateFile.kt @@ -109,12 +109,12 @@ internal object UploadPrivateFile : IActionHandler() { val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, userId) suspendCancellableCoroutine { msgService.sendMsgWithMsgId( - contact, msgIdPair.second, arrayListOf(msgElement) + contact, msgIdPair.qqMsgId, arrayListOf(msgElement) ) { code, reason -> LogCenter.log("私聊文件消息发送异常(code = $code, reason = $reason)") it.resume(null) } - RichMediaUploadHandler.registerListener(msgIdPair.second) { + RichMediaUploadHandler.registerListener(msgIdPair.qqMsgId) { it.resume(this) return@registerListener true } @@ -126,7 +126,7 @@ internal object UploadPrivateFile : IActionHandler() { }.commonFileInfo return ok(data = UploadGroupFile.FileUploadResult( - msgHash = msgIdPair.first, + msgHash = msgIdPair.msgHashId, bizid = info.bizType ?: 0, md5 = info.md5, sha = info.sha, diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/structures/SendMsgResult.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/structures/SendMsgResult.kt new file mode 100644 index 0000000..976185a --- /dev/null +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/structures/SendMsgResult.kt @@ -0,0 +1,8 @@ +package moe.fuqiuluo.shamrock.remote.structures + +data class SendMsgResult( + val msgHashId: Int, + val qqMsgId: Long, + var msgTime: Long, + var isTimeout: Boolean = false +)