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,21 +13,33 @@ 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(
groupId: String, groupId: String,
peerId: String peerId: String
@ -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(
MsgConstant.KCHATTYPETEMPC2CFROMGROUP, TempChatPrepareInfo(
ContactHelper.getUidByUinAsync(peerId = peerId.toLong()), MsgConstant.KCHATTYPETEMPC2CFROMGROUP,
app.getRuntimeService(ITroopMemberNameService::class.java, "all").getTroopMemberNameRemarkFirst(groupId, peerId), ContactHelper.getUidByUinAsync(peerId = peerId.toLong()),
groupId, app.getRuntimeService(ITroopMemberNameService::class.java, "all")
EMPTY_BYTE_ARRAY, .getTroopMemberNameRemarkFirst(groupId, peerId),
app.currentUid, groupId,
"", EMPTY_BYTE_ARRAY,
TempChatGameSession() app.currentUid,
)) { code, reason -> "",
TempChatGameSession()
)
) { 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失败"))
@ -261,7 +313,7 @@ internal object MsgSvc: BaseSvc() {
class MessageCallback( class MessageCallback(
private val peerId: String, private val peerId: String,
var msgHash: Int var msgHash: Int
): IOperateCallback { ) : IOperateCallback {
override fun onResult(code: Int, reason: String?) { override fun onResult(code: Int, reason: String?) {
if (code != 0 && msgHash != 0) { if (code != 0 && msgHash != 0) {
MessageHelper.removeMsgByHashCode(msgHash) MessageHelper.removeMsgByHashCode(msgHash)

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()
@ -227,14 +224,19 @@ internal object MessageMaker {
face.stickerId = "13" face.stickerId = "13"
face.sourceType = 1 face.sourceType = 1
face.stickerType = 2 face.stickerType = 2
face.resultId = Random.nextInt(1 .. 5).toString() face.resultId = Random.nextInt(1..5).toString()
face.surpriseId = "" face.surpriseId = ""
face.randomType = 1 face.randomType = 1
elem.faceElement = face elem.faceElement = face
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()
@ -370,7 +377,7 @@ internal object MessageMaker {
LogCenter.log("无法获取被回复消息", Level.ERROR) LogCenter.log("无法获取被回复消息", Level.ERROR)
} }
if(data.containsKey("text")) { if (data.containsKey("text")) {
data.checkAndThrow("qq", "time", "seq") data.checkAndThrow("qq", "time", "seq")
reply.replayMsgSeq = data["seq"].asLong reply.replayMsgSeq = data["seq"].asLong
reply.sourceMsgText = data["text"].asString reply.sourceMsgText = data["text"].asString
@ -389,21 +396,23 @@ internal object MessageMaker {
): Result<MsgElement> { ): Result<MsgElement> {
data.checkAndThrow("type") data.checkAndThrow("type")
when(val type = data["type"].asString) { when (val type = data["type"].asString) {
"qq" -> { "qq" -> {
data.checkAndThrow("id") data.checkAndThrow("id")
val id = data["id"].asString val id = data["id"].asString
if(!MusicHelper.tryShareQQMusicById(chatType, peerId.toLong(), msgId, id)) { if (!MusicHelper.tryShareQQMusicById(chatType, peerId.toLong(), msgId, id)) {
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
if(!MusicHelper.tryShare163MusicById(chatType, peerId.toLong(), msgId, id)) { if (!MusicHelper.tryShare163MusicById(chatType, peerId.toLong(), msgId, id)) {
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()
@ -544,8 +581,8 @@ internal object MessageMaker {
face.vaspokeName = "" face.vaspokeName = ""
face.vaspokeMinver = "" face.vaspokeMinver = ""
face.pokeStrength = (data["strength"].asIntOrNull ?: data["cnt"].asIntOrNull face.pokeStrength = (data["strength"].asIntOrNull ?: data["cnt"].asIntOrNull
?: data["count"].asIntOrNull ?: data["time"].asIntOrNull ?: 0).also { ?: data["count"].asIntOrNull ?: data["time"].asIntOrNull ?: 0).also {
if(it < 0 || it > 3) throw IllegalParamsException("strength") if (it < 0 || it > 3) throw IllegalParamsException("strength")
} }
face.msgType = 0 face.msgType = 0
face.faceBubbleCount = 0 face.faceBubbleCount = 0
@ -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,15 +617,15 @@ 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
face.randomType = 1 face.randomType = 1
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!!)
} }
@ -711,23 +774,26 @@ internal object MessageMaker {
val qq = data["qq"].asString val qq = data["qq"].asString
val at = TextElement() val at = TextElement()
when(qq) { when (qq) {
"0", "all" -> { "0", "all" -> {
at.content = "@全体成员" at.content = "@全体成员"
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)
@ -813,12 +887,13 @@ internal object MessageMaker {
// QQNTWrapperUtil.CppProxy.copyFile(file.absolutePath, originalPath) // QQNTWrapperUtil.CppProxy.copyFile(file.absolutePath, originalPath)
//} //}
if(!(Transfer with when (chatType) { if (!(Transfer with when (chatType) {
MsgConstant.KCHATTYPEGROUP -> Troop(peerId) MsgConstant.KCHATTYPEGROUP -> Troop(peerId)
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() {
@ -37,14 +39,26 @@ internal object SendForwardMessage : IActionHandler() {
return noParam("detail_type/message_type", session.echo) return noParam("detail_type/message_type", session.echo)
} }
} }
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) { if (data.containsKey("id")) {
LogCenter.log("data字段错误", Level.WARN) val record = MsgSvc.getMsg(data["id"].asInt).getOrElse {
return@map null LogCenter.log("合并转发消息节点消息(id = ${data["id"].asInt})获取失败:$it", Level.WARN)
}
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 return@map null
} }
}.let { node -> uid = record.peerUid
val content = node.second.map { msg -> if (record.chatType == MsgConstant.KCHATTYPEGROUP) groupUin = record.peerUin.toString()
when (msg.asJsonObject["type"].asStringOrNull ?: "text") { PushMsgBody(
"at" -> { head = MessageHead(
buildJsonObject { peerUid = record.senderUid,
put("type", "text") groupInfo = if (record.chatType == MsgConstant.KCHATTYPEGROUP) GroupInfo(
putJsonObject("data") { groupCode = record.peerUin.toULong(),
put( memberCard = record.sendMemberName,
"text", "@${ u1 = 2
msg.asJsonObject["data"].asJsonObject["name"].asStringOrNull.ifNullOrEmpty( ) else null
msg.asJsonObject["data"].asJsonObject["qq"].asString ),
) 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
).also {
if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。")
}.second
)
)
)
} else if (data.containsKey("content")) {
PushMsgBody(
head = MessageHead(
peerUid = data["uid"]?.asString ?: TicketSvc.getUid()
),
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
"voice" -> { )
buildJsonObject { )
put("type", "text") )
putJsonObject("data") { } else {
put("text", "[语音]") LogCenter.log("消息节点缺少id或content字段", Level.WARN)
} null
}
}
"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.qqMsgId == 0L) {
LogCenter.log("合并转发消息节点消息发送失败", Level.WARN)
return@map null
}
result.qqMsgId to node.first
} }
}.filterNotNull() }.filterNotNull().ifEmpty { return logic("消息节点为空", echo) }
val from = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, selfUin)
val to = MessageHelper.generateContact(chatType, peerId, fromId)
val uniseq = MessageHelper.generateMsgId(chatType) val resid = MsgSvc.sendMultiMsg(uid ?: TicketSvc.getUid(), groupUin, msgs)
msgService.multiForwardMsg(ArrayList<MultiMsgInfo>().apply { .getOrElse { return logic(it.message ?: "", echo) }
multiNodes.forEach { add(MultiMsgInfo(it.first, it.second)) } val uniseq = UUID.randomUUID().toString().uppercase()
}.also { it.reverse() }, from, to, MsgSvc.MessageCallback(peerId, uniseq.msgHashId))
val result = MsgSvc.sendToAio(
chatType, peerId,
listOf(
hashMapOf(
"type" to "json", "data" to hashMapOf(
"data" to hashMapOf(
"app" to "com.tencent.multimsg",
"config" to hashMapOf(
"autosize" to 1,
"forward" to 1,
"round" to 1,
"type" to "normal",
"width" to 300
).json,
"desc" to "[聊天记录]",
"extra" to hashMapOf(
"filename" to uniseq,
"tsum" to 2
).json.toString() + "\n",
"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

@ -144,7 +144,7 @@ internal object AioListener : IKernelMsgListener {
} }
MsgConstant.KCHATTYPEGUILD -> { MsgConstant.KCHATTYPEGUILD -> {
LogCenter.log("频道消息(guildId = ${record.guildId}, sender=${record.senderUid}, id = [$msgHash | ${record.msgId}], msg = $rawMsg)") LogCenter.log("频道消息(guildId = ${record.guildId}, sender = ${record.senderUid}, id = [$msgHash | ${record.msgId}], msg = $rawMsg)")
if (!GlobalEventTransmitter.MessageTransmitter if (!GlobalEventTransmitter.MessageTransmitter
.transGuildMessage(record, record.elements, rawMsg, msgHash, postType = postType) .transGuildMessage(record, record.elements, rawMsg, msgHash, postType = postType)
) { ) {
@ -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,16 +65,14 @@ 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 is String -> map[key] = any.json
is String -> map[key] = any.json is Boolean -> map[key] = any.json
is Boolean -> map[key] = any.json is Map<*, *> -> map[key] = (any as Map<String, Any>).json
is Map<*, *> -> map[key] = (any as Map<String, Any>).json is Collection<*> -> map[key] = (any as Collection<Any>).json
is Collection<*> -> map[key] = (any as Collection<Any>).json else -> error("unknown object type: ${any::class.java}")
else -> error("unknown object type: ${any::class.java}")
}
} }
} }
return map.jsonObject return map.jsonObject