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(
@ProtoNumber(1) val rich: RichMessage? = 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(6) val msgTime: Long? = 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(12) val msgRandom: Long = Long.MIN_VALUE,
@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
data class MessageElement(
@ProtoNumber(1) val text: TextElement? = null,
@ProtoNumber(2) val face: FaceElement? = null,
@ProtoNumber(51) val json: JsonElement? = 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(5) val receiver: Long? = null,
@ProtoNumber(6) val receiverUid: String? = null,
@ProtoNumber(7) val forward: MessageForward? = null,
@ProtoNumber(8) val groupInfo: GroupInfo? = null,
)
@Serializable
data class MessageForward(
@ProtoNumber(6) val friendName: String? = null,
)
@Serializable
data class GroupInfo(
@ProtoNumber(1) val groupCode: ULong = ULong.MIN_VALUE,
@ProtoNumber(4) val memberCard: String? = null,
@ProtoNumber(5) val u1: Int? = 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.troop.api.ITroopMemberNameService
import com.tencent.qqnt.kernel.api.IKernelService
import com.tencent.qqnt.kernel.nativeinterface.IOperateCallback
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.kernel.nativeinterface.*
import com.tencent.qqnt.msg.api.IMsgService
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
@ -18,21 +13,33 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.serialization.decodeFromByteArray
import kotlinx.serialization.encodeToByteArray
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.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
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.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.msgService
import protobuf.message.*
import protobuf.message.longmsg.*
import tencent.mobileim.structmsg.structmsg.SystemMsg
import java.util.UUID
import kotlin.collections.slice
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlin.random.Random
import kotlin.random.nextLong
internal object MsgSvc: BaseSvc() {
internal object MsgSvc : BaseSvc() {
suspend fun prepareTempChatFromGroup(
groupId: String,
peerId: String
@ -40,16 +47,19 @@ internal object MsgSvc: BaseSvc() {
LogCenter.log("主动临时消息,创建临时会话。", Level.INFO)
val msgService = app.getRuntimeService(IKernelService::class.java, "all").msgService
?: return Result.failure(Exception("获取消息服务失败"))
msgService.prepareTempChat(TempChatPrepareInfo(
MsgConstant.KCHATTYPETEMPC2CFROMGROUP,
ContactHelper.getUidByUinAsync(peerId = peerId.toLong()),
app.getRuntimeService(ITroopMemberNameService::class.java, "all").getTroopMemberNameRemarkFirst(groupId, peerId),
groupId,
EMPTY_BYTE_ARRAY,
app.currentUid,
"",
TempChatGameSession()
)) { code, reason ->
msgService.prepareTempChat(
TempChatPrepareInfo(
MsgConstant.KCHATTYPETEMPC2CFROMGROUP,
ContactHelper.getUidByUinAsync(peerId = peerId.toLong()),
app.getRuntimeService(ITroopMemberNameService::class.java, "all")
.getTroopMemberNameRemarkFirst(groupId, peerId),
groupId,
EMPTY_BYTE_ARRAY,
app.currentUid,
"",
TempChatGameSession()
)
) { code, reason ->
if (code != 0) {
LogCenter.log("临时会话创建失败: $code, $reason", Level.ERROR)
}
@ -83,7 +93,7 @@ internal object MsgSvc: BaseSvc() {
?: return Result.failure(Exception("没有对应消息映射,消息获取失败"))
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 service = QRoute.api(IMsgService::class.java)
@ -174,7 +184,7 @@ internal object MsgSvc: BaseSvc() {
val mapping = MessageHelper.getMsgMappingByHash(msgHash)
?: 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 ->
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) {
LogCenter.log("sendToAio: " + result.exceptionOrNull()?.stackTraceToString(), Level.ERROR)
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>> {
// 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 sessionService = kernelService.wrapperSession
val msgService = sessionService.msgService
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)
if (msgId < 0) {
return Result.failure(Exception("获取合并转发消息ID失败"))
@ -261,7 +313,7 @@ internal object MsgSvc: BaseSvc() {
class MessageCallback(
private val peerId: String,
var msgHash: Int
): IOperateCallback {
) : IOperateCallback {
override fun onResult(code: Int, reason: String?) {
if (code != 0 && msgHash != 0) {
MessageHelper.removeMsgByHashCode(msgHash)

View File

@ -50,6 +50,14 @@ internal object TicketSvc: BaseSvc() {
return app.longAccountUin
}
fun getUid(): String {
return app.currentUid.ifBlank { "u_" }
}
fun getNickname(): String {
return app.currentNickname
}
fun getCookie(): String {
val uin = getUin()
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.nextInt
internal typealias IMaker = suspend (Int, Long, String, JsonObject) -> Result<MsgElement>
internal typealias IMsgMaker = suspend (Int, Long, String, JsonObject) -> Result<MsgElement>
internal object MessageMaker {
private val makerArray = mutableMapOf(
"text" to MessageMaker::createTextElem,
"face" to MessageMaker::createFaceElem,
"pic" to MessageMaker::createImageElem,
"image" to MessageMaker::createImageElem,
"voice" to MessageMaker::createRecordElem,
"record" to MessageMaker::createRecordElem,
"at" to MessageMaker::createAtElem,
"video" to MessageMaker::createVideoElem,
"markdown" to MessageMaker::createMarkdownElem,
"dice" to MessageMaker::createDiceElem,
"rps" to MessageMaker::createRpsElem,
"poke" to MessageMaker::createPokeElem,
"anonymous" to MessageMaker::createAnonymousElem,
"share" to MessageMaker::createShareElem,
"contact" to MessageMaker::createContactElem,
"location" to MessageMaker::createLocationElem,
"music" to MessageMaker::createMusicElem,
"reply" to MessageMaker::createReplyElem,
"touch" to MessageMaker::createTouchElem,
"weather" to MessageMaker::createWeatherElem,
"json" to MessageMaker::createJsonElem,
"new_dice" to MessageMaker::createNewDiceElem,
"new_rps" to MessageMaker::createNewRpsElem,
"basketball" to MessageMaker::createBasketballElem,
internal object MsgElementMaker {
private val makerArray = hashMapOf(
"text" to MsgElementMaker::createTextElem,
"face" to MsgElementMaker::createFaceElem,
"pic" to MsgElementMaker::createImageElem,
"image" to MsgElementMaker::createImageElem,
"voice" to MsgElementMaker::createRecordElem,
"record" to MsgElementMaker::createRecordElem,
"at" to MsgElementMaker::createAtElem,
"video" to MsgElementMaker::createVideoElem,
"markdown" to MsgElementMaker::createMarkdownElem,
"dice" to MsgElementMaker::createDiceElem,
"rps" to MsgElementMaker::createRpsElem,
"poke" to MsgElementMaker::createPokeElem,
"anonymous" to MsgElementMaker::createAnonymousElem,
"share" to MsgElementMaker::createShareElem,
"contact" to MsgElementMaker::createContactElem,
"location" to MsgElementMaker::createLocationElem,
"music" to MsgElementMaker::createMusicElem,
"reply" to MsgElementMaker::createReplyElem,
"touch" to MsgElementMaker::createTouchElem,
"weather" to MsgElementMaker::createWeatherElem,
"json" to MsgElementMaker::createJsonElem,
"new_dice" to MsgElementMaker::createNewDiceElem,
"new_rps" to MsgElementMaker::createNewRpsElem,
"basketball" to MsgElementMaker::createBasketballElem,
//"node" to MessageMaker::createNodeElem,
//"multi_msg" to MessageMaker::createLongMsgStruct,
"bubble_face" to MessageMaker::createBubbleFaceElem,
"inline_keyboard" to MessageMaker::createInlineKeywordElem
"bubble_face" to MsgElementMaker::createBubbleFaceElem,
"inline_keyboard" to MsgElementMaker::createInlineKeywordElem
)
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)
}
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")
val faceId = data["id"].asInt
val local = QQSysFaceUtil.convertToLocal(faceId)
@ -202,21 +207,13 @@ internal object MessageMaker {
// SendForwardMessage(MsgConstant.KCHATTYPEC2C, TicketSvc.getUin(), data["content"].asJsonArray)
//
// }
/**\
* msgElement.setFaceElement(new FaceElement());
* msgElement.getFaceElement().setFaceIndex(114);
* msgElement.getFaceElement().setFaceText("/篮球");
* msgElement.getFaceElement().setFaceType(3);
* msgElement.getFaceElement().setPackId("1");
* msgElement.getFaceElement().setStickerId("13");
* 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> {
private suspend fun createBasketballElem(
chatType: Int,
msgId: Long,
peerId: String,
data: JsonObject
): Result<MsgElement> {
val elem = MsgElement()
elem.elementType = MsgConstant.KELEMTYPEFACE
val face = FaceElement()
@ -227,14 +224,19 @@ internal object MessageMaker {
face.stickerId = "13"
face.sourceType = 1
face.stickerType = 2
face.resultId = Random.nextInt(1 .. 5).toString()
face.resultId = Random.nextInt(1..5).toString()
face.surpriseId = ""
face.randomType = 1
elem.faceElement = face
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()
elem.elementType = MsgConstant.KELEMTYPEFACE
val face = FaceElement()
@ -252,7 +254,12 @@ internal object MessageMaker {
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()
elem.elementType = MsgConstant.KELEMTYPEFACE
val face = FaceElement()
@ -370,7 +377,7 @@ internal object MessageMaker {
LogCenter.log("无法获取被回复消息", Level.ERROR)
}
if(data.containsKey("text")) {
if (data.containsKey("text")) {
data.checkAndThrow("qq", "time", "seq")
reply.replayMsgSeq = data["seq"].asLong
reply.sourceMsgText = data["text"].asString
@ -389,21 +396,23 @@ internal object MessageMaker {
): Result<MsgElement> {
data.checkAndThrow("type")
when(val type = data["type"].asString) {
when (val type = data["type"].asString) {
"qq" -> {
data.checkAndThrow("id")
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)
}
}
"163" -> {
data.checkAndThrow("id")
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)
}
}
"custom" -> {
data.checkAndThrow("url", "audio", "title")
ArkMsgSvc.tryShareMusic(
@ -418,13 +427,19 @@ internal object MessageMaker {
data["audio"].asString
)
}
else -> LogCenter.log("不支持的音乐分享类型: $type", Level.ERROR)
}
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")
val lat = data["lat"].asString.toDouble()
@ -437,7 +452,12 @@ internal object MessageMaker {
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")
val type = data["type"].asStringOrNull ?: data["kind"].asStringOrNull
val id = data["id"].asString
@ -448,10 +468,12 @@ internal object MessageMaker {
val ark = ArkElement(CardSvc.getSharePrivateArkMsg(id.toLong()), null, null)
elem.arkElement = ark
}
"group" -> {
val ark = ArkElement(GroupSvc.getShareTroopArkMsg(id.toLong()), null, null)
elem.arkElement = ark
}
else -> throw IllegalParamsException("type")
}
@ -460,7 +482,12 @@ internal object MessageMaker {
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")
val url = data["url"].asString
@ -525,11 +552,21 @@ internal object MessageMaker {
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)
}
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")
val elem = MsgElement()
val face = FaceElement()
@ -544,8 +581,8 @@ internal object MessageMaker {
face.vaspokeName = ""
face.vaspokeMinver = ""
face.pokeStrength = (data["strength"].asIntOrNull ?: data["cnt"].asIntOrNull
?: data["count"].asIntOrNull ?: data["time"].asIntOrNull ?: 0).also {
if(it < 0 || it > 3) throw IllegalParamsException("strength")
?: data["count"].asIntOrNull ?: data["time"].asIntOrNull ?: 0).also {
if (it < 0 || it > 3) throw IllegalParamsException("strength")
}
face.msgType = 0
face.faceBubbleCount = 0
@ -556,7 +593,12 @@ internal object MessageMaker {
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")
val serverId = data["id"].asInt
@ -575,15 +617,15 @@ internal object MessageMaker {
face.faceIndex = serverId
face.faceText = QQSysFaceUtil.getFaceDescription(QQSysFaceUtil.convertToLocal(serverId))
if (serverId == 394) {
face.stickerId = 40.toString()
face.stickerId = "40"
face.packId = "1"
face.sourceType = 1
face.stickerType = 3
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) {
face.imageType = 0
face.stickerId = 30.toString()
face.stickerId = "30"
face.packId = "1"
face.sourceType = 1
face.stickerType = 1
@ -597,7 +639,12 @@ internal object MessageMaker {
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()
elem.elementType = MsgConstant.KELEMTYPEMARKETFACE
val market = MarketFaceElement(
@ -612,7 +659,12 @@ internal object MessageMaker {
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()
elem.elementType = MsgConstant.KELEMTYPEMARKETFACE
val market = MarketFaceElement(
@ -627,7 +679,12 @@ internal object MessageMaker {
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")
val elem = MsgElement()
elem.elementType = MsgConstant.KELEMTYPEMARKDOWN
@ -636,7 +693,12 @@ internal object MessageMaker {
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")
val file = data["file"].asString.let {
@ -672,7 +734,8 @@ internal object MessageMaker {
)
if (!QQNTWrapperUtil.CppProxy.fileIsExist(originalPath) || QQNTWrapperUtil.CppProxy.getFileSize(
originalPath
) != file.length()) {
) != file.length()
) {
QQNTWrapperUtil.CppProxy.copyFile(file.absolutePath, originalPath)
AudioUtils.obtainVideoCover(file.absolutePath, thumbPath!!)
}
@ -711,23 +774,26 @@ internal object MessageMaker {
val qq = data["qq"].asString
val at = TextElement()
when(qq) {
when (qq) {
"0", "all" -> {
at.content = "@全体成员"
at.atType = MsgConstant.ATTYPEALL
at.atNtUid = "0"
}
"online" -> {
at.content = "@在线成员"
at.atType = MsgConstant.ATTYPEONLINE
at.atNtUid = "0"
}
"admin" -> {
at.content = "@管理员"
at.atRoleId = 1
at.atType = MsgConstant.ATTYPEROLE
at.atNtUid = "0"
}
else -> {
val name = data["name"].asStringOrNull
if (name == null) {
@ -757,7 +823,12 @@ internal object MessageMaker {
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 {
val md5 = it.replace(regex = "[{}\\-]".toRegex(), replacement = "")
.replace(" ", "")
@ -785,11 +856,13 @@ internal object MessageMaker {
ptt.duration = QRoute.api(IAIOPttApi::class.java)
.getPttFileDuration(file.absolutePath)
}
MediaType.Amr -> {
LogCenter.log({ "Amr: $file" }, Level.DEBUG)
ptt.duration = AudioUtils.getDurationSec(file)
ptt.formatType = MsgConstant.KPTTFORMATTYPEAMR
}
MediaType.Pcm -> {
LogCenter.log({ "Pcm To Silk: $file" }, Level.DEBUG)
val result = AudioUtils.pcmToSilk(file)
@ -797,6 +870,7 @@ internal object MessageMaker {
file = result.first
ptt.formatType = MsgConstant.KPTTFORMATTYPESILK
}
else -> {
LogCenter.log({ "Audio To SILK: $file" }, Level.DEBUG)
val result = AudioUtils.audioToSilk(file)
@ -813,12 +887,13 @@ internal object MessageMaker {
// QQNTWrapperUtil.CppProxy.copyFile(file.absolutePath, originalPath)
//}
if(!(Transfer with when (chatType) {
MsgConstant.KCHATTYPEGROUP -> Troop(peerId)
MsgConstant.KCHATTYPEC2C -> Private(peerId)
MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> Private(peerId)
else -> error("Not supported chatType($chatType) for RecordMsg")
} trans VoiceResource(file))) {
if (!(Transfer with when (chatType) {
MsgConstant.KCHATTYPEGROUP -> Troop(peerId)
MsgConstant.KCHATTYPEC2C -> Private(peerId)
MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> Private(peerId)
else -> error("Not supported chatType($chatType) for RecordMsg")
} trans VoiceResource(file))
) {
return Result.failure(RuntimeException("上传语音失败: $file"))
}
@ -848,7 +923,12 @@ internal object MessageMaker {
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 isFlash = data["flash"].asBooleanOrNull ?: false
val filePath = data["file"].asStringOrNull
@ -889,7 +969,8 @@ internal object MessageMaker {
)
if (!QQNTWrapperUtil.CppProxy.fileIsExist(originalPath) || QQNTWrapperUtil.CppProxy.getFileSize(
originalPath
) != file.length()) {
) != file.length()
) {
val thumbPath = msgService.getRichMediaFilePathForMobileQQSend(
RichMediaFilePathInfo(
2, 0, pic.md5HexStr, file.name, 2, 720, null, "", true
@ -927,7 +1008,12 @@ internal object MessageMaker {
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")
val elem = MsgElement()
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
import com.tencent.mobileqq.qmmkv.QMMKV
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
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.hex2ByteArray
import moe.fuqiuluo.shamrock.tools.json
import mqq.app.MobileQQ
import kotlin.jvm.internal.Intrinsics
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 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.MessageMapping
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.json
import moe.fuqiuluo.shamrock.tools.jsonArray
import protobuf.message.MessageElement
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlin.math.abs
internal object MessageHelper {
@ -39,7 +40,7 @@ internal object MessageHelper {
fromId: String = peerId
): SendMsgResult {
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) {
error("消息合成失败,请查看日志或者检查输入。")
} else if (it.second.isEmpty()) {
@ -82,7 +83,7 @@ internal object MessageHelper {
callback: IOperateCallback
): Result<SendMsgResult> {
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("消息合成失败,请查看日志或者检查输入。")
}.second.filter {
it.elementType != -1
@ -166,7 +167,7 @@ internal object MessageHelper {
fromId: String = peerId
): SendMsgResult {
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("消息合成失败,请查看日志或者检查输入。")
}.second.filter {
it.elementType != -1
@ -224,7 +225,7 @@ internal object MessageHelper {
fromId: String = peerId
): SendMsgResult {
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("消息合成失败,请查看日志或者检查输入。")
}.second.filter {
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>()
var hasActionMsg = false
messageList.forEach {
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) {
try {
val data = msg["data"].asJsonObjectOrNull ?: EmptyJsonObject

View File

@ -1,7 +1,6 @@
package moe.fuqiuluo.shamrock.remote.action.handlers
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MultiMsgInfo
import kotlinx.serialization.json.*
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
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.service.data.ForwardMessageResult
import moe.fuqiuluo.shamrock.tools.*
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
import moe.fuqiuluo.symbols.OneBotHandler
import protobuf.message.*
import protobuf.message.longmsg.PushMsgBody
import java.util.*
import kotlin.random.Random
@OneBotHandler("send_forward_msg")
internal object SendForwardMessage : IActionHandler() {
@ -37,14 +39,26 @@ internal object SendForwardMessage : IActionHandler() {
return noParam("detail_type/message_type", session.echo)
}
}
val peerId = when(chatType) {
MsgConstant.KCHATTYPEGROUP -> session.getStringOrNull("group_id") ?: return noParam("group_id", session.echo)
MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getStringOrNull("user_id") ?: return noParam("user_id", session.echo)
val peerId = when (chatType) {
MsgConstant.KCHATTYPEGROUP -> session.getStringOrNull("group_id") ?: return noParam(
"group_id",
session.echo
)
MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getStringOrNull("user_id")
?: return noParam("user_id", session.echo)
else -> error("unknown chat type: $chatType")
}
val fromId = when(chatType) {
MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getStringOrNull("group_id") ?: return noParam("group_id", session.echo)
MsgConstant.KCHATTYPEC2C -> session.getStringOrNull("user_id") ?: return noParam("user_id", session.echo)
val fromId = when (chatType) {
MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getStringOrNull("group_id")
?: return noParam("group_id", session.echo)
MsgConstant.KCHATTYPEC2C -> session.getStringOrNull("user_id") ?: return noParam(
"user_id",
session.echo
)
else -> error("unknown chat type: $chatType")
}
return if (session.isArray("messages")) {
@ -68,103 +82,184 @@ internal object SendForwardMessage : IActionHandler() {
echo: JsonElement = EmptyJsonString
): String {
kotlin.runCatching {
val kernelService = NTServiceFetcher.kernelService
val sessionService = kernelService.wrapperSession
val msgService = sessionService.msgService
val selfUin = TicketSvc.getUin()
var uid: String? = null
var groupUin: String? = null
val multiNodes = messages.map {
if (it.asJsonObject["type"].asStringOrNull != "node") {
LogCenter.log("包含非node类型节点", Level.WARN)
return@map null
}
if (it.asJsonObject["data"] !is JsonObject) {
LogCenter.log("data字段错误", Level.WARN)
return@map null
}
it.asJsonObject["data"].asJsonObject.let { data ->
if (data.containsKey("id")) {
val record = MsgSvc.getMsg(data["id"].asInt).getOrNull()
if (record == null) {
LogCenter.log("合并转发消息节点消息获取失败:${data["id"]}", Level.WARN)
return@map null
} else {
record.peerName to record.toSegments().map { segment ->
segment.toJson()
}.json
}
} else if (data.containsKey("content")) {
(data["name"].asStringOrNull ?: "Anno") to when (val raw = data["content"]) {
is JsonObject -> raw.asJsonArray
is JsonArray -> raw.asJsonArray
else -> MessageHelper.decodeCQCode(raw.asString)
}
} else {
LogCenter.log("消息节点缺少id或content字段", Level.WARN)
var i = -1
val desc = MutableList(messages.size) { "" }
val msgs = messages.map { msg ->
val data = msg.asJsonObject["data"].asJsonObject
if (data.containsKey("id")) {
val record = MsgSvc.getMsg(data["id"].asInt).getOrElse {
LogCenter.log("合并转发消息节点消息(id = ${data["id"].asInt})获取失败:$it", Level.WARN)
return@map 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
)
}"
)
uid = record.peerUid
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
).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] += "[合并转发消息]"
}
}
}
}
"voice" -> {
buildJsonObject {
put("type", "text")
putJsonObject("data") {
put("text", "[语音]")
}
}
}
"node" -> {
LogCenter.log("合并转发消息暂时不支持嵌套", Level.WARN)
buildJsonObject {
put("type", "text")
putJsonObject("data") {
put("text", "[合并转发消息]")
}
}
}
else -> msg
}
}.json
val result = MessageHelper.sendMessageNoCb(MsgConstant.KCHATTYPEC2C, selfUin, content)
if (result.qqMsgId == 0L) {
LogCenter.log("合并转发消息节点消息发送失败", Level.WARN)
return@map null
}
result.qqMsgId to node.first
).also {
if (it.second.isEmpty() && !it.first) error("消息合成失败,请查看日志或者检查输入。")
}.second
)
)
)
} else {
LogCenter.log("消息节点缺少id或content字段", Level.WARN)
null
}
}.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)
msgService.multiForwardMsg(ArrayList<MultiMsgInfo>().apply {
multiNodes.forEach { add(MultiMsgInfo(it.first, it.second)) }
}.also { it.reverse() }, from, to, MsgSvc.MessageCallback(peerId, uniseq.msgHashId))
val resid = MsgSvc.sendMultiMsg(uid ?: TicketSvc.getUid(), groupUin, msgs)
.getOrElse { return logic(it.message ?: "", echo) }
val uniseq = UUID.randomUUID().toString().uppercase()
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(
ForwardMessageResult(
msgId = uniseq.msgHashId,
forwardId = ""
msgId = result.msgHashId,
forwardId = resid
), echo = echo
)
}.onFailure {

View File

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

View File

@ -144,7 +144,7 @@ internal object AioListener : IKernelMsgListener {
}
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
.transGuildMessage(record, record.elements, rawMsg, msgHash, postType = postType)
) {
@ -169,7 +169,6 @@ internal object AioListener : IKernelMsgListener {
GlobalScope.launch {
try {
val msgHash = MessageHelper.generateMsgIdHash(record.chatType, record.msgId)
val peerId = when (record.chatType) {
MsgConstant.KCHATTYPEGUILD -> record.guildId
else -> record.peerUin.toString()

View File

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