refactor send_forward_msg(暂未完成 请勿使用)

This commit is contained in:
Simplxs 2024-02-19 04:13:18 +08:00
parent a5cdd64686
commit 1c65aab673
No known key found for this signature in database
GPG Key ID: E23537FF14DD6507
17 changed files with 714 additions and 233 deletions

View File

@ -7,4 +7,5 @@ import kotlinx.serialization.protobuf.ProtoNumber
data class MessageBody( data class MessageBody(
@ProtoNumber(1) val rich: RichMessage? = null, @ProtoNumber(1) val rich: RichMessage? = null,
@ProtoNumber(2) val rawBuffer: ByteArray? = null, @ProtoNumber(2) val rawBuffer: ByteArray? = null,
@ProtoNumber(3) val MsgEncryptContent: ByteArray? = null
) )

View File

@ -11,8 +11,20 @@ data class MessageContent(
@ProtoNumber(5) val msgSeq: Long = Long.MIN_VALUE, @ProtoNumber(5) val msgSeq: Long = Long.MIN_VALUE,
@ProtoNumber(6) val msgTime: Long? = null, @ProtoNumber(6) val msgTime: Long? = null,
@ProtoNumber(7) val u2: Int? = null, @ProtoNumber(7) val u2: Int? = null,
@ProtoNumber(8) val u6: Int? = null,
@ProtoNumber(9) val u7: Int? = null,
@ProtoNumber(11) val u3: Long? = null, @ProtoNumber(11) val u3: Long? = null,
@ProtoNumber(12) val msgRandom: Long = Long.MIN_VALUE, @ProtoNumber(12) val msgRandom: Long = Long.MIN_VALUE,
@ProtoNumber(14) val u4: Long? = null, @ProtoNumber(14) val u4: Long? = null,
@ProtoNumber(28) val u5: Long? = null, @ProtoNumber(15) val forwardHead: ForwardHead? = null,
@ProtoNumber(28) val u5: Long? = null
)
@Serializable
data class ForwardHead(
@ProtoNumber(1) val u1: Int? = null,
@ProtoNumber(2) val u2: Int? = null,
@ProtoNumber(3) val u3: Int? = null,
@ProtoNumber(4) val u4: String? = null,
@ProtoNumber(5) val Avatar: String? = null
) )

View File

@ -7,6 +7,7 @@ import protobuf.message.element.*
@Serializable @Serializable
data class MessageElement( data class MessageElement(
@ProtoNumber(1) val text: TextElement? = null, @ProtoNumber(1) val text: TextElement? = null,
@ProtoNumber(2) val face: FaceElement? = null,
@ProtoNumber(51) val json: JsonElement? = null, @ProtoNumber(51) val json: JsonElement? = null,
@ProtoNumber(53) val commElem: CommonElement? = null, @ProtoNumber(53) val commElem: CommonElement? = null,
) )

View File

@ -13,12 +13,19 @@ data class MessageHead(
@ProtoNumber(4) val appId: Int = Int.MIN_VALUE, @ProtoNumber(4) val appId: Int = Int.MIN_VALUE,
@ProtoNumber(5) val receiver: Long? = null, @ProtoNumber(5) val receiver: Long? = null,
@ProtoNumber(6) val receiverUid: String? = null, @ProtoNumber(6) val receiverUid: String? = null,
@ProtoNumber(7) val forward: MessageForward? = null,
@ProtoNumber(8) val groupInfo: GroupInfo? = null, @ProtoNumber(8) val groupInfo: GroupInfo? = null,
) )
@Serializable
data class MessageForward(
@ProtoNumber(6) val friendName: String? = null,
)
@Serializable @Serializable
data class GroupInfo( data class GroupInfo(
@ProtoNumber(1) val groupCode: ULong = ULong.MIN_VALUE, @ProtoNumber(1) val groupCode: ULong = ULong.MIN_VALUE,
@ProtoNumber(4) val memberCard: String? = null, @ProtoNumber(4) val memberCard: String? = null,
@ProtoNumber(5) val u1: Int? = null,
@ProtoNumber(7) val groupName: String? = null, @ProtoNumber(7) val groupName: String? = null,
) )

View File

@ -0,0 +1,9 @@
package protobuf.message.element
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class FaceElement(
@ProtoNumber(1) val id: Int? = null,
)

View File

@ -0,0 +1,62 @@
@file:OptIn(ExperimentalSerializationApi::class)
package protobuf.message.longmsg
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class LongMsgSettings(
@ProtoNumber(1) val field1: Int? = null,
@ProtoNumber(2) val field2: Int? = null,
@ProtoNumber(3) val field3: Int? = null,
@ProtoNumber(4) val field4: Int? = null,
)
@Serializable
data class LongMsgUid(
@ProtoNumber(2) val uid: String? = null,
)
@Serializable
data class RecvLongMsgInfo(
@ProtoNumber(1) val uid: LongMsgUid? = null,
@ProtoNumber(2) val resId: String? = null,
@ProtoNumber(3) val acquire: Boolean? = null,
)
@Serializable
data class SendLongMsgInfo(
@ProtoNumber(1) val type: Int? = null,
@ProtoNumber(2) val uid: LongMsgUid? = null,
@ProtoNumber(3) val groupUin: Int? = null,
@ProtoNumber(4) val payload: ByteArray? = null,
)
@Serializable
data class LongMsgReq(
@ProtoNumber(1) val recvInfo: RecvLongMsgInfo? = null,
@ProtoNumber(2) val sendInfo: SendLongMsgInfo? = null,
@ProtoNumber(15) val setting: LongMsgSettings? = null,
)
@Serializable
data class LongMsgRsp(
@ProtoNumber(1) val recvResult: RecvLongMsgResult? = null,
@ProtoNumber(2) val sendResult: SendLongMsgResult? = null,
@ProtoNumber(15) val setting: LongMsgSettings? = null
) {
companion object {
@Serializable
data class SendLongMsgResult(
@ProtoNumber(3) val resId: String? = null,
)
@Serializable
data class RecvLongMsgResult(
@ProtoNumber(3) val resId: String? = null,
@ProtoNumber(4) val payload: ByteArray? = null,
)
}
}

View File

@ -0,0 +1,32 @@
@file:OptIn(ExperimentalSerializationApi::class)
package protobuf.message.longmsg
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
import protobuf.message.MessageBody
import protobuf.message.MessageContent
import protobuf.message.MessageHead
@Serializable
data class PushMsgBody(
@ProtoNumber(1) val head: MessageHead? = null,
@ProtoNumber(2) val content: MessageContent? = null,
@ProtoNumber(3) val body: MessageBody? = null
)
@Serializable
data class LongMsgContent(
@ProtoNumber(1) val body: List<PushMsgBody>? = null
)
@Serializable
data class LongMsgAction(
@ProtoNumber(1) val command: String? = null,
@ProtoNumber(2) val data: LongMsgContent? = null
)
@Serializable
data class LongMsgPayload(
@ProtoNumber(2) val action: LongMsgAction? = null
)

View File

@ -5,12 +5,7 @@ package moe.fuqiuluo.qqinterface.servlet
import com.tencent.mobileqq.qroute.QRoute import com.tencent.mobileqq.qroute.QRoute
import com.tencent.mobileqq.troop.api.ITroopMemberNameService import com.tencent.mobileqq.troop.api.ITroopMemberNameService
import com.tencent.qqnt.kernel.api.IKernelService import com.tencent.qqnt.kernel.api.IKernelService
import com.tencent.qqnt.kernel.nativeinterface.IOperateCallback import com.tencent.qqnt.kernel.nativeinterface.*
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
import com.tencent.qqnt.kernel.nativeinterface.TempChatGameSession
import com.tencent.qqnt.kernel.nativeinterface.TempChatInfo
import com.tencent.qqnt.kernel.nativeinterface.TempChatPrepareInfo
import com.tencent.qqnt.msg.api.IMsgService import com.tencent.qqnt.msg.api.IMsgService
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
@ -18,19 +13,31 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.serialization.decodeFromByteArray
import kotlinx.serialization.encodeToByteArray
import kotlinx.serialization.json.JsonArray 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.shamrock.helper.ContactHelper import moe.fuqiuluo.shamrock.helper.ContactHelper
import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.helper.MessageHelper import moe.fuqiuluo.shamrock.helper.MessageHelper
import moe.fuqiuluo.shamrock.helper.SendMsgException import moe.fuqiuluo.shamrock.helper.MessageHelper.messageArrayToMessageElements
import moe.fuqiuluo.shamrock.remote.structures.SendMsgResult import moe.fuqiuluo.shamrock.remote.structures.SendMsgResult
import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY import moe.fuqiuluo.shamrock.tools.*
import moe.fuqiuluo.shamrock.utils.DeflateTools
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
import moe.fuqiuluo.shamrock.xposed.helper.msgService 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 java.util.UUID
import kotlin.collections.slice
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
import kotlin.random.Random
import kotlin.random.nextLong
internal object MsgSvc : BaseSvc() { internal object MsgSvc : BaseSvc() {
suspend fun prepareTempChatFromGroup( suspend fun prepareTempChatFromGroup(
@ -40,16 +47,19 @@ internal object MsgSvc: BaseSvc() {
LogCenter.log("主动临时消息,创建临时会话。", Level.INFO) LogCenter.log("主动临时消息,创建临时会话。", Level.INFO)
val msgService = app.getRuntimeService(IKernelService::class.java, "all").msgService val msgService = app.getRuntimeService(IKernelService::class.java, "all").msgService
?: return Result.failure(Exception("获取消息服务失败")) ?: return Result.failure(Exception("获取消息服务失败"))
msgService.prepareTempChat(TempChatPrepareInfo( msgService.prepareTempChat(
TempChatPrepareInfo(
MsgConstant.KCHATTYPETEMPC2CFROMGROUP, MsgConstant.KCHATTYPETEMPC2CFROMGROUP,
ContactHelper.getUidByUinAsync(peerId = peerId.toLong()), ContactHelper.getUidByUinAsync(peerId = peerId.toLong()),
app.getRuntimeService(ITroopMemberNameService::class.java, "all").getTroopMemberNameRemarkFirst(groupId, peerId), app.getRuntimeService(ITroopMemberNameService::class.java, "all")
.getTroopMemberNameRemarkFirst(groupId, peerId),
groupId, groupId,
EMPTY_BYTE_ARRAY, EMPTY_BYTE_ARRAY,
app.currentUid, app.currentUid,
"", "",
TempChatGameSession() TempChatGameSession()
)) { code, reason -> )
) { code, reason ->
if (code != 0) { if (code != 0) {
LogCenter.log("临时会话创建失败: $code, $reason", Level.ERROR) LogCenter.log("临时会话创建失败: $code, $reason", Level.ERROR)
} }
@ -83,7 +93,7 @@ internal object MsgSvc: BaseSvc() {
?: return Result.failure(Exception("没有对应消息映射,消息获取失败")) ?: return Result.failure(Exception("没有对应消息映射,消息获取失败"))
val peerId = mapping.peerId val peerId = mapping.peerId
val contact = MessageHelper.generateContact(mapping.chatType, peerId, mapping.subPeerId ?: "") val contact = MessageHelper.generateContact(mapping.chatType, peerId, mapping.subPeerId)
val msg = withTimeoutOrNull(5000) { val msg = withTimeoutOrNull(5000) {
val service = QRoute.api(IMsgService::class.java) val service = QRoute.api(IMsgService::class.java)
@ -174,7 +184,7 @@ internal object MsgSvc: BaseSvc() {
val mapping = MessageHelper.getMsgMappingByHash(msgHash) val mapping = MessageHelper.getMsgMappingByHash(msgHash)
?: return -1 to "无法找到消息映射" ?: return -1 to "无法找到消息映射"
val contact = MessageHelper.generateContact(mapping.chatType, mapping.peerId, mapping.subPeerId ?: "") val contact = MessageHelper.generateContact(mapping.chatType, mapping.peerId, mapping.subPeerId)
return suspendCancellableCoroutine { continuation -> return suspendCancellableCoroutine { continuation ->
msgService.recallMsg(contact, arrayListOf(mapping.qqMsgId)) { code, why -> msgService.recallMsg(contact, arrayListOf(mapping.qqMsgId)) { code, why ->
@ -204,7 +214,8 @@ internal object MsgSvc: BaseSvc() {
} }
} }
} }
val result = MessageHelper.sendMessageWithoutMsgId(chatType, peedId, message, fromId, MessageCallback(peedId, 0)) val result =
MessageHelper.sendMessageWithoutMsgId(chatType, peedId, message, fromId, MessageCallback(peedId, 0))
if (result.isFailure) { if (result.isFailure) {
LogCenter.log("sendToAio: " + result.exceptionOrNull()?.stackTraceToString(), Level.ERROR) LogCenter.log("sendToAio: " + result.exceptionOrNull()?.stackTraceToString(), Level.ERROR)
return result return result
@ -220,13 +231,54 @@ internal object MsgSvc: BaseSvc() {
} }
} }
suspend fun sendMultiMsg(
uid: String,
groupUin: String?,
messages: List<PushMsgBody>,
): Result<String> {
val payload = LongMsgPayload(
action = LongMsgAction(
command = "MultiMsg",
data = LongMsgContent(
messages
)
)
)
val req = LongMsgReq(
sendInfo = SendLongMsgInfo(
type = if (groupUin == null) 1 else 3,
uid = LongMsgUid(groupUin ?: uid),
groupUin = groupUin?.toInt(),
payload = DeflateTools.gzip(ProtoBuf.encodeToByteArray(payload))
),
setting = LongMsgSettings(
field1 = 4,
field2 = 2,
field3 = 9,
field4 = 0
)
)
val buffer = sendBufferAW(
"trpc.group.long_msg_interface.MsgService.SsoSendLongMsg",
true,
ProtoBuf.encodeToByteArray(req)
) ?: return Result.failure(Exception("unable to upload multi message"))
val rsp = ProtoBuf.decodeFromByteArray<LongMsgRsp>(buffer.slice(4))
return rsp.sendResult?.resId?.let { Result.success(it) } ?: Result.failure(Exception("unable to upload multi message"))
}
suspend fun getMultiMsg(resId: String): Result<List<MsgRecord>> { suspend fun getMultiMsg(resId: String): Result<List<MsgRecord>> {
// 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 kernelService = NTServiceFetcher.kernelService
val sessionService = kernelService.wrapperSession val sessionService = kernelService.wrapperSession
val msgService = sessionService.msgService val msgService = sessionService.msgService
val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, TicketSvc.getUin()) 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 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) val msgId = PacketSvc.fakeSelfRecvJsonMsg(msgService, content)
if (msgId < 0) { if (msgId < 0) {
return Result.failure(Exception("获取合并转发消息ID失败")) return Result.failure(Exception("获取合并转发消息ID失败"))

View File

@ -50,6 +50,14 @@ internal object TicketSvc: BaseSvc() {
return app.longAccountUin return app.longAccountUin
} }
fun getUid(): String {
return app.currentUid.ifBlank { "u_" }
}
fun getNickname(): String {
return app.currentNickname
}
fun getCookie(): String { fun getCookie(): String {
val uin = getUin() val uin = getUin()
val skey = getRealSkey(uin) val skey = getRealSkey(uin)

View File

@ -0,0 +1,96 @@
package moe.fuqiuluo.qqinterface.servlet.msg
import kotlinx.serialization.json.JsonObject
import moe.fuqiuluo.shamrock.helper.ParamsException
import moe.fuqiuluo.shamrock.tools.asInt
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.TextElement
internal typealias IMessageMaker = suspend (Int, Long, String, JsonObject) -> Result<MessageElement>
internal object MessageElementMaker {
private val makerArray = hashMapOf(
"text" to MessageElementMaker::createTextElem,
"face" to MessageElementMaker::createFaceElem,
// "pic" to MessageElementMaker::createImageElem,
// "image" to MessageElementMaker::createImageElem,
// "voice" to MessageElementMaker::createRecordElem,
// "record" to MessageElementMaker::createRecordElem,
// "at" to MessageElementMaker::createAtElem,
// "video" to MessageElementMaker::createVideoElem,
// "markdown" to MessageElementMaker::createMarkdownElem,
// "dice" to MessageElementMaker::createDiceElem,
// "rps" to MessageElementMaker::createRpsElem,
// "poke" to MessageElementMaker::createPokeElem,
// "anonymous" to MessageElementMaker::createAnonymousElem,
// "share" to MessageElementMaker::createShareElem,
// "contact" to MessageElementMaker::createContactElem,
// "location" to MessageElementMaker::createLocationElem,
// "music" to MessageElementMaker::createMusicElem,
// "reply" to MessageElementMaker::createReplyElem,
// "touch" to MessageElementMaker::createTouchElem,
// "weather" to MessageElementMaker::createWeatherElem,
"json" to MessageElementMaker::createJsonElem,
//"new_dice" to MessageElementMaker::createNewDiceElem,
//"new_rps" to MessageElementMaker::createNewRpsElem,
//"basketball" to MessageElementMaker::createBasketballElem,
//"node" to MessageMaker::createNodeElem,
//"multi_msg" to MessageMaker::createLongMsgStruct,
//"bubble_face" to MessageElementMaker::createBubbleFaceElem,
)
private suspend fun createTextElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MessageElement> {
data.checkAndThrow("text")
val elem = MessageElement(
text = TextElement(data["text"].asString)
)
return Result.success(elem)
}
private suspend fun createFaceElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MessageElement> {
data.checkAndThrow("id")
val elem = MessageElement(
face = FaceElement(data["id"].asInt)
)
return Result.success(elem)
}
private suspend fun createJsonElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MessageElement> {
data.checkAndThrow("data")
val elem = MessageElement(
json = protobuf.message.element.JsonElement(
data = DeflateTools.compress(data.toString().toByteArray())
)
)
return Result.success(elem)
}
private fun JsonObject.checkAndThrow(vararg key: String) {
key.forEach {
if (!containsKey(it)) throw ParamsException(it)
}
}
operator fun get(type: String): IMessageMaker? = makerArray[type]
}

View File

@ -82,38 +82,38 @@ import kotlin.math.roundToInt
import kotlin.random.Random import kotlin.random.Random
import kotlin.random.nextInt import kotlin.random.nextInt
internal typealias IMaker = suspend (Int, Long, String, JsonObject) -> Result<MsgElement> internal typealias IMsgMaker = suspend (Int, Long, String, JsonObject) -> Result<MsgElement>
internal object MessageMaker { internal object MsgElementMaker {
private val makerArray = mutableMapOf( private val makerArray = hashMapOf(
"text" to MessageMaker::createTextElem, "text" to MsgElementMaker::createTextElem,
"face" to MessageMaker::createFaceElem, "face" to MsgElementMaker::createFaceElem,
"pic" to MessageMaker::createImageElem, "pic" to MsgElementMaker::createImageElem,
"image" to MessageMaker::createImageElem, "image" to MsgElementMaker::createImageElem,
"voice" to MessageMaker::createRecordElem, "voice" to MsgElementMaker::createRecordElem,
"record" to MessageMaker::createRecordElem, "record" to MsgElementMaker::createRecordElem,
"at" to MessageMaker::createAtElem, "at" to MsgElementMaker::createAtElem,
"video" to MessageMaker::createVideoElem, "video" to MsgElementMaker::createVideoElem,
"markdown" to MessageMaker::createMarkdownElem, "markdown" to MsgElementMaker::createMarkdownElem,
"dice" to MessageMaker::createDiceElem, "dice" to MsgElementMaker::createDiceElem,
"rps" to MessageMaker::createRpsElem, "rps" to MsgElementMaker::createRpsElem,
"poke" to MessageMaker::createPokeElem, "poke" to MsgElementMaker::createPokeElem,
"anonymous" to MessageMaker::createAnonymousElem, "anonymous" to MsgElementMaker::createAnonymousElem,
"share" to MessageMaker::createShareElem, "share" to MsgElementMaker::createShareElem,
"contact" to MessageMaker::createContactElem, "contact" to MsgElementMaker::createContactElem,
"location" to MessageMaker::createLocationElem, "location" to MsgElementMaker::createLocationElem,
"music" to MessageMaker::createMusicElem, "music" to MsgElementMaker::createMusicElem,
"reply" to MessageMaker::createReplyElem, "reply" to MsgElementMaker::createReplyElem,
"touch" to MessageMaker::createTouchElem, "touch" to MsgElementMaker::createTouchElem,
"weather" to MessageMaker::createWeatherElem, "weather" to MsgElementMaker::createWeatherElem,
"json" to MessageMaker::createJsonElem, "json" to MsgElementMaker::createJsonElem,
"new_dice" to MessageMaker::createNewDiceElem, "new_dice" to MsgElementMaker::createNewDiceElem,
"new_rps" to MessageMaker::createNewRpsElem, "new_rps" to MsgElementMaker::createNewRpsElem,
"basketball" to MessageMaker::createBasketballElem, "basketball" to MsgElementMaker::createBasketballElem,
//"node" to MessageMaker::createNodeElem, //"node" to MessageMaker::createNodeElem,
//"multi_msg" to MessageMaker::createLongMsgStruct, //"multi_msg" to MessageMaker::createLongMsgStruct,
"bubble_face" to MessageMaker::createBubbleFaceElem, "bubble_face" to MsgElementMaker::createBubbleFaceElem,
"inline_keyboard" to MessageMaker::createInlineKeywordElem "inline_keyboard" to MsgElementMaker::createInlineKeywordElem
) )
private suspend fun createInlineKeywordElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createInlineKeywordElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> {
@ -169,7 +169,12 @@ internal object MessageMaker {
return Result.success(elem) return Result.success(elem)
} }
private suspend fun createBubbleFaceElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createBubbleFaceElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
data.checkAndThrow("id", "count") data.checkAndThrow("id", "count")
val faceId = data["id"].asInt val faceId = data["id"].asInt
val local = QQSysFaceUtil.convertToLocal(faceId) val local = QQSysFaceUtil.convertToLocal(faceId)
@ -202,21 +207,13 @@ internal object MessageMaker {
// SendForwardMessage(MsgConstant.KCHATTYPEC2C, TicketSvc.getUin(), data["content"].asJsonArray) // SendForwardMessage(MsgConstant.KCHATTYPEC2C, TicketSvc.getUin(), data["content"].asJsonArray)
// //
// } // }
/**\
* msgElement.setFaceElement(new FaceElement()); private suspend fun createBasketballElem(
* msgElement.getFaceElement().setFaceIndex(114); chatType: Int,
* msgElement.getFaceElement().setFaceText("/篮球"); msgId: Long,
* msgElement.getFaceElement().setFaceType(3); peerId: String,
* msgElement.getFaceElement().setPackId("1"); data: JsonObject
* msgElement.getFaceElement().setStickerId("13"); ): Result<MsgElement> {
* msgElement.getFaceElement().setRandomType(1);
* msgElement.getFaceElement().setImageType(1);
* msgElement.getFaceElement().setStickerType(2);
* msgElement.getFaceElement().setSourceType(1);
* msgElement.getFaceElement().setSurpriseId("");
* msgElement.getFaceElement().setResultId(String.valueOf(new Random().nextInt(5) + 1));
*/
private suspend fun createBasketballElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> {
val elem = MsgElement() val elem = MsgElement()
elem.elementType = MsgConstant.KELEMTYPEFACE elem.elementType = MsgConstant.KELEMTYPEFACE
val face = FaceElement() val face = FaceElement()
@ -234,7 +231,12 @@ internal object MessageMaker {
return Result.success(elem) return Result.success(elem)
} }
private suspend fun createNewRpsElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createNewRpsElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
val elem = MsgElement() val elem = MsgElement()
elem.elementType = MsgConstant.KELEMTYPEFACE elem.elementType = MsgConstant.KELEMTYPEFACE
val face = FaceElement() val face = FaceElement()
@ -252,7 +254,12 @@ internal object MessageMaker {
return Result.success(elem) return Result.success(elem)
} }
private suspend fun createNewDiceElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createNewDiceElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
val elem = MsgElement() val elem = MsgElement()
elem.elementType = MsgConstant.KELEMTYPEFACE elem.elementType = MsgConstant.KELEMTYPEFACE
val face = FaceElement() val face = FaceElement()
@ -397,6 +404,7 @@ internal object MessageMaker {
LogCenter.log("无法发送QQ音乐分享", Level.ERROR) LogCenter.log("无法发送QQ音乐分享", Level.ERROR)
} }
} }
"163" -> { "163" -> {
data.checkAndThrow("id") data.checkAndThrow("id")
val id = data["id"].asString val id = data["id"].asString
@ -404,6 +412,7 @@ internal object MessageMaker {
LogCenter.log("无法发送网易云音乐分享", Level.ERROR) LogCenter.log("无法发送网易云音乐分享", Level.ERROR)
} }
} }
"custom" -> { "custom" -> {
data.checkAndThrow("url", "audio", "title") data.checkAndThrow("url", "audio", "title")
ArkMsgSvc.tryShareMusic( ArkMsgSvc.tryShareMusic(
@ -418,13 +427,19 @@ internal object MessageMaker {
data["audio"].asString data["audio"].asString
) )
} }
else -> LogCenter.log("不支持的音乐分享类型: $type", Level.ERROR) else -> LogCenter.log("不支持的音乐分享类型: $type", Level.ERROR)
} }
return Result.failure(ActionMsgException) return Result.failure(ActionMsgException)
} }
private suspend fun createLocationElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createLocationElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
data.checkAndThrow("lat", "lon") data.checkAndThrow("lat", "lon")
val lat = data["lat"].asString.toDouble() val lat = data["lat"].asString.toDouble()
@ -437,7 +452,12 @@ internal object MessageMaker {
return Result.failure(ActionMsgException) return Result.failure(ActionMsgException)
} }
private suspend fun createContactElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createContactElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
data.checkAndThrow("id") data.checkAndThrow("id")
val type = data["type"].asStringOrNull ?: data["kind"].asStringOrNull val type = data["type"].asStringOrNull ?: data["kind"].asStringOrNull
val id = data["id"].asString val id = data["id"].asString
@ -448,10 +468,12 @@ internal object MessageMaker {
val ark = ArkElement(CardSvc.getSharePrivateArkMsg(id.toLong()), null, null) val ark = ArkElement(CardSvc.getSharePrivateArkMsg(id.toLong()), null, null)
elem.arkElement = ark elem.arkElement = ark
} }
"group" -> { "group" -> {
val ark = ArkElement(GroupSvc.getShareTroopArkMsg(id.toLong()), null, null) val ark = ArkElement(GroupSvc.getShareTroopArkMsg(id.toLong()), null, null)
elem.arkElement = ark elem.arkElement = ark
} }
else -> throw IllegalParamsException("type") else -> throw IllegalParamsException("type")
} }
@ -460,7 +482,12 @@ internal object MessageMaker {
return Result.success(elem) return Result.success(elem)
} }
private suspend fun createShareElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createShareElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
data.checkAndThrow("title", "url") data.checkAndThrow("title", "url")
val url = data["url"].asString val url = data["url"].asString
@ -525,11 +552,21 @@ internal object MessageMaker {
return Result.failure(ActionMsgException) return Result.failure(ActionMsgException)
} }
private suspend fun createAnonymousElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createAnonymousElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
return Result.failure(ActionMsgException) return Result.failure(ActionMsgException)
} }
private suspend fun createPokeElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createPokeElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
data.checkAndThrow("type", "id") data.checkAndThrow("type", "id")
val elem = MsgElement() val elem = MsgElement()
val face = FaceElement() val face = FaceElement()
@ -556,7 +593,12 @@ internal object MessageMaker {
return Result.success(elem) return Result.success(elem)
} }
private suspend fun createFaceElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createFaceElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
data.checkAndThrow("id") data.checkAndThrow("id")
val serverId = data["id"].asInt val serverId = data["id"].asInt
@ -575,7 +617,7 @@ internal object MessageMaker {
face.faceIndex = serverId face.faceIndex = serverId
face.faceText = QQSysFaceUtil.getFaceDescription(QQSysFaceUtil.convertToLocal(serverId)) face.faceText = QQSysFaceUtil.getFaceDescription(QQSysFaceUtil.convertToLocal(serverId))
if (serverId == 394) { if (serverId == 394) {
face.stickerId = 40.toString() face.stickerId = "40"
face.packId = "1" face.packId = "1"
face.sourceType = 1 face.sourceType = 1
face.stickerType = 3 face.stickerType = 3
@ -583,7 +625,7 @@ internal object MessageMaker {
face.resultId = data["result"].asStringOrNull ?: Random.nextInt(1..5).toString() face.resultId = data["result"].asStringOrNull ?: Random.nextInt(1..5).toString()
} else if (big) { } else if (big) {
face.imageType = 0 face.imageType = 0
face.stickerId = 30.toString() face.stickerId = "30"
face.packId = "1" face.packId = "1"
face.sourceType = 1 face.sourceType = 1
face.stickerType = 1 face.stickerType = 1
@ -597,7 +639,12 @@ internal object MessageMaker {
return Result.success(elem) return Result.success(elem)
} }
private suspend fun createRpsElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createRpsElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
val elem = MsgElement() val elem = MsgElement()
elem.elementType = MsgConstant.KELEMTYPEMARKETFACE elem.elementType = MsgConstant.KELEMTYPEMARKETFACE
val market = MarketFaceElement( val market = MarketFaceElement(
@ -612,7 +659,12 @@ internal object MessageMaker {
return Result.success(elem) return Result.success(elem)
} }
private suspend fun createDiceElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createDiceElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
val elem = MsgElement() val elem = MsgElement()
elem.elementType = MsgConstant.KELEMTYPEMARKETFACE elem.elementType = MsgConstant.KELEMTYPEMARKETFACE
val market = MarketFaceElement( val market = MarketFaceElement(
@ -627,7 +679,12 @@ internal object MessageMaker {
return Result.success(elem) return Result.success(elem)
} }
private suspend fun createMarkdownElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createMarkdownElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
data.checkAndThrow("content") data.checkAndThrow("content")
val elem = MsgElement() val elem = MsgElement()
elem.elementType = MsgConstant.KELEMTYPEMARKDOWN elem.elementType = MsgConstant.KELEMTYPEMARKDOWN
@ -636,7 +693,12 @@ internal object MessageMaker {
return Result.success(elem) return Result.success(elem)
} }
private suspend fun createVideoElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createVideoElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
data.checkAndThrow("file") data.checkAndThrow("file")
val file = data["file"].asString.let { val file = data["file"].asString.let {
@ -672,7 +734,8 @@ internal object MessageMaker {
) )
if (!QQNTWrapperUtil.CppProxy.fileIsExist(originalPath) || QQNTWrapperUtil.CppProxy.getFileSize( if (!QQNTWrapperUtil.CppProxy.fileIsExist(originalPath) || QQNTWrapperUtil.CppProxy.getFileSize(
originalPath originalPath
) != file.length()) { ) != file.length()
) {
QQNTWrapperUtil.CppProxy.copyFile(file.absolutePath, originalPath) QQNTWrapperUtil.CppProxy.copyFile(file.absolutePath, originalPath)
AudioUtils.obtainVideoCover(file.absolutePath, thumbPath!!) AudioUtils.obtainVideoCover(file.absolutePath, thumbPath!!)
} }
@ -717,17 +780,20 @@ internal object MessageMaker {
at.atType = MsgConstant.ATTYPEALL at.atType = MsgConstant.ATTYPEALL
at.atNtUid = "0" at.atNtUid = "0"
} }
"online" -> { "online" -> {
at.content = "@在线成员" at.content = "@在线成员"
at.atType = MsgConstant.ATTYPEONLINE at.atType = MsgConstant.ATTYPEONLINE
at.atNtUid = "0" at.atNtUid = "0"
} }
"admin" -> { "admin" -> {
at.content = "@管理员" at.content = "@管理员"
at.atRoleId = 1 at.atRoleId = 1
at.atType = MsgConstant.ATTYPEROLE at.atType = MsgConstant.ATTYPEROLE
at.atNtUid = "0" at.atNtUid = "0"
} }
else -> { else -> {
val name = data["name"].asStringOrNull val name = data["name"].asStringOrNull
if (name == null) { if (name == null) {
@ -757,7 +823,12 @@ internal object MessageMaker {
return Result.success(elem) return Result.success(elem)
} }
private suspend fun createRecordElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createRecordElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
var file = data["file"].asStringOrNull?.let { var file = data["file"].asStringOrNull?.let {
val md5 = it.replace(regex = "[{}\\-]".toRegex(), replacement = "") val md5 = it.replace(regex = "[{}\\-]".toRegex(), replacement = "")
.replace(" ", "") .replace(" ", "")
@ -785,11 +856,13 @@ internal object MessageMaker {
ptt.duration = QRoute.api(IAIOPttApi::class.java) ptt.duration = QRoute.api(IAIOPttApi::class.java)
.getPttFileDuration(file.absolutePath) .getPttFileDuration(file.absolutePath)
} }
MediaType.Amr -> { MediaType.Amr -> {
LogCenter.log({ "Amr: $file" }, Level.DEBUG) LogCenter.log({ "Amr: $file" }, Level.DEBUG)
ptt.duration = AudioUtils.getDurationSec(file) ptt.duration = AudioUtils.getDurationSec(file)
ptt.formatType = MsgConstant.KPTTFORMATTYPEAMR ptt.formatType = MsgConstant.KPTTFORMATTYPEAMR
} }
MediaType.Pcm -> { MediaType.Pcm -> {
LogCenter.log({ "Pcm To Silk: $file" }, Level.DEBUG) LogCenter.log({ "Pcm To Silk: $file" }, Level.DEBUG)
val result = AudioUtils.pcmToSilk(file) val result = AudioUtils.pcmToSilk(file)
@ -797,6 +870,7 @@ internal object MessageMaker {
file = result.first file = result.first
ptt.formatType = MsgConstant.KPTTFORMATTYPESILK ptt.formatType = MsgConstant.KPTTFORMATTYPESILK
} }
else -> { else -> {
LogCenter.log({ "Audio To SILK: $file" }, Level.DEBUG) LogCenter.log({ "Audio To SILK: $file" }, Level.DEBUG)
val result = AudioUtils.audioToSilk(file) val result = AudioUtils.audioToSilk(file)
@ -818,7 +892,8 @@ internal object MessageMaker {
MsgConstant.KCHATTYPEC2C -> Private(peerId) MsgConstant.KCHATTYPEC2C -> Private(peerId)
MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> Private(peerId) MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> Private(peerId)
else -> error("Not supported chatType($chatType) for RecordMsg") else -> error("Not supported chatType($chatType) for RecordMsg")
} trans VoiceResource(file))) { } trans VoiceResource(file))
) {
return Result.failure(RuntimeException("上传语音失败: $file")) return Result.failure(RuntimeException("上传语音失败: $file"))
} }
@ -848,7 +923,12 @@ internal object MessageMaker {
return Result.success(elem) return Result.success(elem)
} }
private suspend fun createImageElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createImageElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
val isOriginal = data["original"].asBooleanOrNull ?: true val isOriginal = data["original"].asBooleanOrNull ?: true
val isFlash = data["flash"].asBooleanOrNull ?: false val isFlash = data["flash"].asBooleanOrNull ?: false
val filePath = data["file"].asStringOrNull val filePath = data["file"].asStringOrNull
@ -889,7 +969,8 @@ internal object MessageMaker {
) )
if (!QQNTWrapperUtil.CppProxy.fileIsExist(originalPath) || QQNTWrapperUtil.CppProxy.getFileSize( if (!QQNTWrapperUtil.CppProxy.fileIsExist(originalPath) || QQNTWrapperUtil.CppProxy.getFileSize(
originalPath originalPath
) != file.length()) { ) != file.length()
) {
val thumbPath = msgService.getRichMediaFilePathForMobileQQSend( val thumbPath = msgService.getRichMediaFilePathForMobileQQSend(
RichMediaFilePathInfo( RichMediaFilePathInfo(
2, 0, pic.md5HexStr, file.name, 2, 720, null, "", true 2, 0, pic.md5HexStr, file.name, 2, 720, null, "", true
@ -927,7 +1008,12 @@ internal object MessageMaker {
return Result.success(elem) return Result.success(elem)
} }
private suspend fun createTextElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> { private suspend fun createTextElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
data.checkAndThrow("text") data.checkAndThrow("text")
val elem = MsgElement() val elem = MsgElement()
elem.elementType = MsgConstant.KELEMTYPETEXT elem.elementType = MsgConstant.KELEMTYPETEXT
@ -943,5 +1029,5 @@ internal object MessageMaker {
} }
} }
operator fun get(type: String): IMaker? = makerArray[type] operator fun get(type: String): IMsgMaker? = makerArray[type]
} }

View File

@ -1,6 +1,5 @@
package moe.fuqiuluo.qqinterface.servlet.msg.convert package moe.fuqiuluo.qqinterface.servlet.msg.convert
import com.tencent.mobileqq.qmmkv.QMMKV
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MsgElement import com.tencent.qqnt.kernel.nativeinterface.MsgElement
import kotlinx.serialization.json.add import kotlinx.serialization.json.add
@ -19,8 +18,6 @@ import moe.fuqiuluo.shamrock.tools.asJsonObject
import moe.fuqiuluo.shamrock.tools.asString import moe.fuqiuluo.shamrock.tools.asString
import moe.fuqiuluo.shamrock.tools.hex2ByteArray import moe.fuqiuluo.shamrock.tools.hex2ByteArray
import moe.fuqiuluo.shamrock.tools.json import moe.fuqiuluo.shamrock.tools.json
import mqq.app.MobileQQ
import kotlin.jvm.internal.Intrinsics
internal sealed class MessageElemConverter: IMessageConvert { internal sealed class MessageElemConverter: IMessageConvert {
/** /**

View File

@ -16,7 +16,8 @@ import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
import moe.fuqiuluo.qqinterface.servlet.MsgSvc import moe.fuqiuluo.qqinterface.servlet.MsgSvc
import moe.fuqiuluo.qqinterface.servlet.msg.MessageMaker import moe.fuqiuluo.qqinterface.servlet.msg.MessageElementMaker
import moe.fuqiuluo.qqinterface.servlet.msg.MsgElementMaker
import moe.fuqiuluo.shamrock.helper.db.MessageDB import moe.fuqiuluo.shamrock.helper.db.MessageDB
import moe.fuqiuluo.shamrock.helper.db.MessageMapping import moe.fuqiuluo.shamrock.helper.db.MessageMapping
import moe.fuqiuluo.shamrock.remote.structures.SendMsgResult import moe.fuqiuluo.shamrock.remote.structures.SendMsgResult
@ -26,8 +27,8 @@ import moe.fuqiuluo.shamrock.tools.asJsonObjectOrNull
import moe.fuqiuluo.shamrock.tools.asString import moe.fuqiuluo.shamrock.tools.asString
import moe.fuqiuluo.shamrock.tools.json import moe.fuqiuluo.shamrock.tools.json
import moe.fuqiuluo.shamrock.tools.jsonArray import moe.fuqiuluo.shamrock.tools.jsonArray
import protobuf.message.MessageElement
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlin.math.abs import kotlin.math.abs
internal object MessageHelper { internal object MessageHelper {
@ -39,7 +40,7 @@ internal object MessageHelper {
fromId: String = peerId fromId: String = peerId
): SendMsgResult { ): SendMsgResult {
val uniseq = generateMsgId(chatType) val uniseq = generateMsgId(chatType)
val msg = messageArrayToMessageElements(chatType, uniseq.qqMsgId, peerId, decodeCQCode(message)).also { val msg = messageArrayToMsgElements(chatType, uniseq.qqMsgId, peerId, decodeCQCode(message)).also {
if (it.second.isEmpty() && !it.first) { if (it.second.isEmpty() && !it.first) {
error("消息合成失败,请查看日志或者检查输入。") error("消息合成失败,请查看日志或者检查输入。")
} else if (it.second.isEmpty()) { } else if (it.second.isEmpty()) {
@ -82,7 +83,7 @@ internal object MessageHelper {
callback: IOperateCallback callback: IOperateCallback
): Result<SendMsgResult> { ): Result<SendMsgResult> {
val uniseq = generateMsgId(chatType) val uniseq = generateMsgId(chatType)
val msg = messageArrayToMessageElements(chatType, uniseq.qqMsgId, peerId, message).also { val msg = messageArrayToMsgElements(chatType, uniseq.qqMsgId, peerId, message).also {
if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。") if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。")
}.second.filter { }.second.filter {
it.elementType != -1 it.elementType != -1
@ -166,7 +167,7 @@ internal object MessageHelper {
fromId: String = peerId fromId: String = peerId
): SendMsgResult { ): SendMsgResult {
val uniseq = generateMsgId(chatType) val uniseq = generateMsgId(chatType)
val msg = messageArrayToMessageElements(chatType, uniseq.qqMsgId, peerId, message).also { val msg = messageArrayToMsgElements(chatType, uniseq.qqMsgId, peerId, message).also {
if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。") if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。")
}.second.filter { }.second.filter {
it.elementType != -1 it.elementType != -1
@ -224,7 +225,7 @@ internal object MessageHelper {
fromId: String = peerId fromId: String = peerId
): SendMsgResult { ): SendMsgResult {
val uniseq = generateMsgId(chatType) val uniseq = generateMsgId(chatType)
val msg = messageArrayToMessageElements(chatType, uniseq.qqMsgId, peerId, message).also { val msg = messageArrayToMsgElements(chatType, uniseq.qqMsgId, peerId, message).also {
if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。") if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。")
}.second.filter { }.second.filter {
it.elementType != -1 it.elementType != -1
@ -276,12 +277,41 @@ internal object MessageHelper {
} }
} }
suspend fun messageArrayToMessageElements(chatType: Int, msgId: Long, targetUin: String, messageList: JsonArray): Pair<Boolean, ArrayList<MsgElement>> { suspend fun messageArrayToMsgElements(chatType: Int, msgId: Long, targetUin: String, messageList: JsonArray): Pair<Boolean, ArrayList<MsgElement>> {
val msgList = arrayListOf<MsgElement>() val msgList = arrayListOf<MsgElement>()
var hasActionMsg = false var hasActionMsg = false
messageList.forEach { messageList.forEach {
val msg = it.jsonObject val msg = it.jsonObject
val maker = MessageMaker[msg["type"].asString] val maker = MsgElementMaker[msg["type"].asString]
if (maker != null) {
try {
val data = msg["data"].asJsonObjectOrNull ?: EmptyJsonObject
maker(chatType, msgId, targetUin, data).onSuccess { msgElem ->
msgList.add(msgElem)
}.onFailure {
if (it.javaClass != ActionMsgException::class.java) {
throw it
} else {
hasActionMsg = true
}
}
} catch (e: Throwable) {
LogCenter.log(e.stackTraceToString(), Level.ERROR)
}
} else {
LogCenter.log("不支持的消息类型: ${msg["type"].asString}", Level.ERROR)
return false to arrayListOf()
}
}
return hasActionMsg to msgList
}
suspend fun messageArrayToMessageElements(chatType: Int, msgId: Long, targetUin: String, messageList: JsonArray): Pair<Boolean, ArrayList<MessageElement>> {
val msgList = arrayListOf<MessageElement>()
var hasActionMsg = false
messageList.forEach {
val msg = it.jsonObject
val maker = MessageElementMaker[msg["type"].asString]
if (maker != null) { if (maker != null) {
try { try {
val data = msg["data"].asJsonObjectOrNull ?: EmptyJsonObject val data = msg["data"].asJsonObjectOrNull ?: EmptyJsonObject

View File

@ -1,7 +1,6 @@
package moe.fuqiuluo.shamrock.remote.action.handlers package moe.fuqiuluo.shamrock.remote.action.handlers
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MultiMsgInfo
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
import moe.fuqiuluo.qqinterface.servlet.MsgSvc import moe.fuqiuluo.qqinterface.servlet.MsgSvc
import moe.fuqiuluo.qqinterface.servlet.TicketSvc import moe.fuqiuluo.qqinterface.servlet.TicketSvc
@ -14,8 +13,11 @@ import moe.fuqiuluo.shamrock.remote.action.ActionSession
import moe.fuqiuluo.shamrock.remote.action.IActionHandler import moe.fuqiuluo.shamrock.remote.action.IActionHandler
import moe.fuqiuluo.shamrock.remote.service.data.ForwardMessageResult import moe.fuqiuluo.shamrock.remote.service.data.ForwardMessageResult
import moe.fuqiuluo.shamrock.tools.* import moe.fuqiuluo.shamrock.tools.*
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
import moe.fuqiuluo.symbols.OneBotHandler import moe.fuqiuluo.symbols.OneBotHandler
import protobuf.message.*
import protobuf.message.longmsg.PushMsgBody
import java.util.*
import kotlin.random.Random
@OneBotHandler("send_forward_msg") @OneBotHandler("send_forward_msg")
internal object SendForwardMessage : IActionHandler() { internal object SendForwardMessage : IActionHandler() {
@ -38,13 +40,25 @@ internal object SendForwardMessage : IActionHandler() {
} }
} }
val peerId = when (chatType) { val peerId = when (chatType) {
MsgConstant.KCHATTYPEGROUP -> session.getStringOrNull("group_id") ?: return noParam("group_id", session.echo) MsgConstant.KCHATTYPEGROUP -> session.getStringOrNull("group_id") ?: return noParam(
MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getStringOrNull("user_id") ?: return noParam("user_id", session.echo) "group_id",
session.echo
)
MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getStringOrNull("user_id")
?: return noParam("user_id", session.echo)
else -> error("unknown chat type: $chatType") else -> error("unknown chat type: $chatType")
} }
val fromId = when (chatType) { val fromId = when (chatType) {
MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getStringOrNull("group_id") ?: return noParam("group_id", session.echo) MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getStringOrNull("group_id")
MsgConstant.KCHATTYPEC2C -> session.getStringOrNull("user_id") ?: return noParam("user_id", session.echo) ?: return noParam("group_id", session.echo)
MsgConstant.KCHATTYPEC2C -> session.getStringOrNull("user_id") ?: return noParam(
"user_id",
session.echo
)
else -> error("unknown chat type: $chatType") else -> error("unknown chat type: $chatType")
} }
return if (session.isArray("messages")) { return if (session.isArray("messages")) {
@ -68,103 +82,184 @@ internal object SendForwardMessage : IActionHandler() {
echo: JsonElement = EmptyJsonString echo: JsonElement = EmptyJsonString
): String { ): String {
kotlin.runCatching { kotlin.runCatching {
val kernelService = NTServiceFetcher.kernelService var uid: String? = null
val sessionService = kernelService.wrapperSession var groupUin: String? = null
val msgService = sessionService.msgService
val selfUin = TicketSvc.getUin()
val multiNodes = messages.map { var i = -1
if (it.asJsonObject["type"].asStringOrNull != "node") { val desc = MutableList(messages.size) { "" }
LogCenter.log("包含非node类型节点", Level.WARN)
return@map null val msgs = messages.map { msg ->
} val data = msg.asJsonObject["data"].asJsonObject
if (it.asJsonObject["data"] !is JsonObject) {
LogCenter.log("data字段错误", Level.WARN)
return@map null
}
it.asJsonObject["data"].asJsonObject.let { data ->
if (data.containsKey("id")) { if (data.containsKey("id")) {
val record = MsgSvc.getMsg(data["id"].asInt).getOrNull() val record = MsgSvc.getMsg(data["id"].asInt).getOrElse {
if (record == null) { LogCenter.log("合并转发消息节点消息(id = ${data["id"].asInt})获取失败:$it", Level.WARN)
LogCenter.log("合并转发消息节点消息获取失败:${data["id"]}", Level.WARN)
return@map null return@map null
} else { }
record.peerName to record.toSegments().map { segment -> uid = record.peerUid
segment.toJson() if (record.chatType == MsgConstant.KCHATTYPEGROUP) groupUin = record.peerUin.toString()
PushMsgBody(
head = MessageHead(
peerUid = record.senderUid,
groupInfo = if (record.chatType == MsgConstant.KCHATTYPEGROUP) GroupInfo(
groupCode = record.peerUin.toULong(),
memberCard = record.sendMemberName,
u1 = 2
) else null
),
content = MessageContent(
msgType = record.msgType,
msgViaRandom = record.msgId,
msgSeq = record.msgSeq,
msgTime = record.msgTime,
u2 = 1,
u6 = 0,
u7 = 0,
forwardHead = ForwardHead(
u1 = 0,
u2 = 0,
u3 = if (record.chatType == MsgConstant.KCHATTYPEGROUP) 0 else 2,
u4 = "",
Avatar = ""
)
),
body = MessageBody(
rich = RichMessage(
elements = MessageHelper.messageArrayToMessageElements(
record.chatType,
record.msgId,
record.peerUin.toString(),
record.elements.toSegments(record.chatType, record.peerUin.toString(), "0").also {
desc[++i] = record.peerName + ": "
}.map {
when (it.type) {
"text" -> desc[i] += it.data["text"] as String
"at" -> desc[i] += "@${it.data["name"] as String? ?: it.data["qq"] as String}"
"face" -> desc[i] += "[表情]"
"voice" -> desc[i] += "[语音]"
"node" -> desc[i] += "[合并转发消息]"
}
it.toJson()
}.json }.json
} ).also {
if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。")
}.second
)
)
)
} else if (data.containsKey("content")) { } else if (data.containsKey("content")) {
(data["name"].asStringOrNull ?: "Anno") to when (val raw = data["content"]) { PushMsgBody(
is JsonObject -> raw.asJsonArray head = MessageHead(
is JsonArray -> raw.asJsonArray peerUid = data["uid"]?.asString ?: TicketSvc.getUid()
else -> MessageHelper.decodeCQCode(raw.asString) ),
content = MessageContent(
msgType = 529,
msgViaRandom = 4,
msgSeq = data["seq"]?.asLong ?: 0,
msgTime = System.currentTimeMillis() / 1000,
u2 = 1,
u6 = 0,
u7 = 0,
forwardHead = ForwardHead(
u1 = 0,
u2 = 0,
u3 = 2,
u4 = "",
Avatar = ""
)
),
body = MessageBody(
rich = RichMessage(
elements = MessageHelper.messageArrayToMessageElements(
1,
Random.nextLong(),
data["uin"]?.asString ?: TicketSvc.getUin(),
when (data["content"]) {
is JsonObject -> listOf(data["content"] as JsonObject).json
is JsonArray -> data["content"] as JsonArray
else -> MessageHelper.decodeCQCode(data["content"].asString)
}.also {
desc[++i] = "${
data["name"].asStringOrNull ?: data["uin"].asStringOrNull
?: TicketSvc.getNickname()
}: "
}.onEach {
when (it.asJsonObject["type"].asString) {
"text" -> desc[i] += it.asJsonObject["data"].asJsonObject["text"].asString
"at" -> desc[i] += "@${it.asJsonObject["data"].asJsonObject["name"].asStringOrNull ?: it.asJsonObject["data"].asJsonObject["qq"].asString}"
"face" -> desc[i] += "[表情]"
"voice" -> desc[i] += "[语音]"
"node" -> desc[i] += "[合并转发消息]"
} }
}
).also {
if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。")
}.second
)
)
)
} else { } else {
LogCenter.log("消息节点缺少id或content字段", Level.WARN) LogCenter.log("消息节点缺少id或content字段", Level.WARN)
return@map null null
}
}.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
)
}"
)
}
}
} }
}.filterNotNull().ifEmpty { return logic("消息节点为空", echo) }
"voice" -> {
buildJsonObject {
put("type", "text")
putJsonObject("data") {
put("text", "[语音]")
}
}
}
"node" -> { val resid = MsgSvc.sendMultiMsg(uid ?: TicketSvc.getUid(), groupUin, msgs)
LogCenter.log("合并转发消息暂时不支持嵌套", Level.WARN) .getOrElse { return logic(it.message ?: "", echo) }
buildJsonObject { val uniseq = UUID.randomUUID().toString().uppercase()
put("type", "text")
putJsonObject("data") {
put("text", "[合并转发消息]")
}
}
}
else -> msg val result = MsgSvc.sendToAio(
} chatType, peerId,
}.json listOf(
hashMapOf(
val result = MessageHelper.sendMessageNoCb(MsgConstant.KCHATTYPEC2C, selfUin, content) "type" to "json", "data" to hashMapOf(
if (result.qqMsgId == 0L) { "data" to hashMapOf(
LogCenter.log("合并转发消息节点消息发送失败", Level.WARN) "app" to "com.tencent.multimsg",
return@map null "config" to hashMapOf(
} "autosize" to 1,
result.qqMsgId to node.first "forward" to 1,
} "round" to 1,
}.filterNotNull() "type" to "normal",
"width" to 300
val from = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, selfUin) ).json,
val to = MessageHelper.generateContact(chatType, peerId, fromId) "desc" to "[聊天记录]",
"extra" to hashMapOf(
val uniseq = MessageHelper.generateMsgId(chatType) "filename" to uniseq,
msgService.multiForwardMsg(ArrayList<MultiMsgInfo>().apply { "tsum" to 2
multiNodes.forEach { add(MultiMsgInfo(it.first, it.second)) } ).json.toString() + "\n",
}.also { it.reverse() }, from, to, MsgSvc.MessageCallback(peerId, uniseq.msgHashId)) "meta" to hashMapOf(
"detail" to hashMapOf(
"news" to desc.slice(0..if (i < 3) i else 3)
.map { hashMapOf("text" to it).json }.json,
"resid" to resid,
"source" to "群聊的聊天记录",
"summary" to "查看${msgs.size}条转发消息",
"uniseq" to uniseq
).json
).json,
"prompt" to "[聊天记录]",
"ver" to "0.0.0.5",
"view" to "contact"
).json,
"resid" to resid
).json
).json
).json, fromId, 3
).getOrElse { return logic(it.message ?: "", echo) }
return ok( return ok(
ForwardMessageResult( ForwardMessageResult(
msgId = uniseq.msgHashId, msgId = result.msgHashId,
forwardId = "" forwardId = resid
), echo = echo ), echo = echo
) )
}.onFailure { }.onFailure {

View File

@ -120,18 +120,14 @@ internal object SendMessage: IActionHandler() {
//if (!ContactHelper.checkContactAvailable(chatType, peerId)) { //if (!ContactHelper.checkContactAvailable(chatType, peerId)) {
// return logic("contact is not found", echo = echo) // return logic("contact is not found", echo = echo)
//} //}
val result = MsgSvc.sendToAio(chatType, peerId, message, fromId = fromId, retryCnt) val result = MsgSvc.sendToAio(chatType, peerId, message, fromId = fromId, retryCnt).getOrElse { return logic(it.message ?: "", echo) }
if (result.isFailure) { if (result.msgHashId <= 0) {
return logic(result.exceptionOrNull()?.message ?: "", echo)
}
val sendMsgResult = result.getOrThrow()
if (sendMsgResult.msgHashId <= 0) {
return logic("send message failed", echo = echo) return logic("send message failed", echo = echo)
} }
recallDuration?.let { autoRecall(sendMsgResult.msgHashId, it) } recallDuration?.let { autoRecall(result.msgHashId, it) }
return ok(MessageResult( return ok(MessageResult(
msgId = sendMsgResult.msgHashId, msgId = result.msgHashId,
time = (sendMsgResult.msgTime * 0.001).toLong() time = (result.msgTime * 0.001).toLong()
), echo) ), echo)
} }

View File

@ -169,7 +169,6 @@ internal object AioListener : IKernelMsgListener {
GlobalScope.launch { GlobalScope.launch {
try { try {
val msgHash = MessageHelper.generateMsgIdHash(record.chatType, record.msgId) val msgHash = MessageHelper.generateMsgIdHash(record.chatType, record.msgId)
val peerId = when (record.chatType) { val peerId = when (record.chatType) {
MsgConstant.KCHATTYPEGUILD -> record.guildId MsgConstant.KCHATTYPEGUILD -> record.guildId
else -> record.peerUin.toString() else -> record.peerUin.toString()

View File

@ -65,7 +65,6 @@ val Map<String, Any>.json: JsonObject
get() { get() {
val map = hashMapOf<String, JsonElement>() val map = hashMapOf<String, JsonElement>()
forEach { (key, any) -> forEach { (key, any) ->
if (any != null) {
when (any) { when (any) {
is JsonElement -> map[key] = any is JsonElement -> map[key] = any
is Number -> map[key] = any.json is Number -> map[key] = any.json
@ -76,7 +75,6 @@ val Map<String, Any>.json: JsonObject
else -> error("unknown object type: ${any::class.java}") else -> error("unknown object type: ${any::class.java}")
} }
} }
}
return map.jsonObject return map.jsonObject
} }