mirror of
https://github.com/whitechi73/OpenShamrock.git
synced 2024-08-14 13:12:17 +08:00
refactor send_forward_msg
This commit is contained in:
parent
4dc83fdeba
commit
ec56e32be1
@ -7,9 +7,9 @@ import kotlinx.serialization.protobuf.ProtoNumber
|
||||
data class Ptt(
|
||||
@ProtoNumber(1) var fileType: UInt?=null,
|
||||
@ProtoNumber(2) var srcUin: ULong?=null,
|
||||
@ProtoNumber(3) var fileUuid: ByteArray?=null,
|
||||
@ProtoNumber(3) var fileUuid: String?=null,
|
||||
@ProtoNumber(4) var fileMd5: ByteArray?=null,
|
||||
@ProtoNumber(5) var fileName: ByteArray?=null,
|
||||
@ProtoNumber(5) var fileName: String?=null,
|
||||
@ProtoNumber(6) var fileSize: UInt?=null,
|
||||
@ProtoNumber(7) var reserve: ByteArray?=null,
|
||||
@ProtoNumber(8) var fileId: UInt?=null,
|
||||
@ -22,11 +22,19 @@ data class Ptt(
|
||||
@ProtoNumber(15) var magicPttIndex: UInt?=null,
|
||||
@ProtoNumber(16) var voiceSwitch: UInt?=null,
|
||||
@ProtoNumber(17) var pttUrl: ByteArray?=null,
|
||||
@ProtoNumber(18) var groupFileKey: ByteArray?=null,
|
||||
@ProtoNumber(18) var groupFileKey: String?=null,
|
||||
@ProtoNumber(19) var time: UInt?=null,
|
||||
@ProtoNumber(20) var downPara: ByteArray?=null,
|
||||
@ProtoNumber(29) var format: UInt?=null,
|
||||
@ProtoNumber(30) var pbReserve: ByteArray?=null,
|
||||
@ProtoNumber(30) var pbReserve: PbReserve?=null,
|
||||
@ProtoNumber(31) var rptPttUrls: List<String>? = null,
|
||||
@ProtoNumber(32) var downloadFlag: UInt?=null,
|
||||
){
|
||||
companion object{
|
||||
@Serializable
|
||||
data class PbReserve(
|
||||
@ProtoNumber(2) var magic: Int?=null,
|
||||
@ProtoNumber(7) var reserve: Int?=null,
|
||||
)
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@ import kotlinx.serialization.protobuf.ProtoNumber
|
||||
@Serializable
|
||||
data class RichText(
|
||||
@ProtoNumber(1) val attr: Attr? = null,
|
||||
@ProtoNumber(2) val elements: List<Elem>? = null,
|
||||
@ProtoNumber(2) var elements: List<Elem>? = null,
|
||||
@ProtoNumber(3) val not_online_file: NotOnlineFile? = null,
|
||||
@ProtoNumber(4) val ptt: Ptt? = null,
|
||||
@ProtoNumber(5) val tmp_ptt: TmpPtt? = null,
|
||||
|
@ -9,7 +9,9 @@ import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import moe.fuqiuluo.qqinterface.servlet.msg.MessageSegment
|
||||
import moe.fuqiuluo.qqinterface.servlet.msg.toJson
|
||||
import moe.fuqiuluo.qqinterface.servlet.msg.toListMap
|
||||
import moe.fuqiuluo.qqinterface.servlet.msg.toSegments
|
||||
import moe.fuqiuluo.shamrock.helper.ContactHelper
|
||||
@ -25,10 +27,12 @@ import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
||||
import moe.fuqiuluo.shamrock.xposed.helper.msgService
|
||||
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||
import protobuf.auto.toByteArray
|
||||
import protobuf.message.PushMsgBody
|
||||
import protobuf.message.*
|
||||
import protobuf.message.longmsg.*
|
||||
import java.util.*
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import kotlin.random.Random
|
||||
|
||||
internal object MsgSvc : BaseSvc() {
|
||||
private suspend fun prepareTempChatFromGroup(
|
||||
@ -207,45 +211,195 @@ internal object MsgSvc : BaseSvc() {
|
||||
}
|
||||
val result =
|
||||
MessageHelper.sendMessageWithoutMsgId(chatType, peedId, message, fromId, MessageCallback(peedId, 0))
|
||||
if (result.isFailure) {
|
||||
LogCenter.log("sendToAio: " + result.exceptionOrNull()?.stackTraceToString(), Level.ERROR)
|
||||
return result
|
||||
}
|
||||
val sendResult = result.getOrThrow()
|
||||
return if (sendResult.isTimeout) {
|
||||
.getOrElse { return Result.failure(it) }
|
||||
return if (result.isTimeout) {
|
||||
// 发送失败,可能网络问题出现红色感叹号,重试
|
||||
// 例如 rich media transfer failed
|
||||
delay(100)
|
||||
MessageHelper.resendMsg(chatType, peedId, fromId, sendResult.qqMsgId, retryCnt, sendResult.msgHashId)
|
||||
MessageHelper.resendMsg(chatType, peedId, fromId, result.qqMsgId, retryCnt, result.msgHashId)
|
||||
} else {
|
||||
result
|
||||
Result.success(result)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun uploadMultiMsg(
|
||||
uid: String,
|
||||
groupUin: String?,
|
||||
messages: List<PushMsgBody>,
|
||||
): Result<String> {
|
||||
chatType: Int,
|
||||
peerId: String,
|
||||
fromId: String,
|
||||
messages: JsonArray,
|
||||
retryCnt: Int,
|
||||
): Result<MessageSegment> {
|
||||
var i = -1
|
||||
val desc = MutableList(messages.size) { "" }
|
||||
val forwardMsg = mutableMapOf<String, String>()
|
||||
|
||||
val msgs = messages.mapNotNull { msg ->
|
||||
kotlin.runCatching {
|
||||
val data = msg.asJsonObject["data"].asJsonObject
|
||||
if (data.containsKey("id")) {
|
||||
val record = getMsg(data["id"].asInt).getOrElse {
|
||||
error("合并转发消息节点消息(id = ${data["id"].asInt})获取失败:$it")
|
||||
}
|
||||
PushMsgBody(
|
||||
msgHead = ResponseHead(
|
||||
peerUid = record.senderUid,
|
||||
receiverUid = record.peerUid,
|
||||
forward = ResponseForward(
|
||||
friendName = record.sendNickName
|
||||
),
|
||||
responseGrp = if (record.chatType == MsgConstant.KCHATTYPEGROUP) ResponseGrp(
|
||||
groupCode = record.peerUin.toULong(),
|
||||
memberCard = record.sendMemberName,
|
||||
u1 = 2
|
||||
) else null
|
||||
),
|
||||
contentHead = ContentHead(
|
||||
msgType = when (record.chatType) {
|
||||
MsgConstant.KCHATTYPEC2C -> 9
|
||||
MsgConstant.KCHATTYPEGROUP -> 82
|
||||
else -> throw UnsupportedOperationException(
|
||||
"Unsupported chatType: $chatType"
|
||||
)
|
||||
},
|
||||
msgSubType = if (record.chatType == MsgConstant.KCHATTYPEC2C) 175 else null,
|
||||
divSeq = if (record.chatType == MsgConstant.KCHATTYPEC2C) 175 else null,
|
||||
msgViaRandom = record.msgId,
|
||||
sequence = record.msgSeq, // idk what this is(i++)
|
||||
msgTime = record.msgTime,
|
||||
u2 = 1,
|
||||
u6 = 0,
|
||||
u7 = 0,
|
||||
msgSeq = if (record.chatType == MsgConstant.KCHATTYPEC2C) record.msgSeq else null, // seq for dm
|
||||
forwardHead = ForwardHead(
|
||||
u1 = 0,
|
||||
u2 = 0,
|
||||
u3 = 0,
|
||||
ub641 = "",
|
||||
avatar = ""
|
||||
)
|
||||
),
|
||||
body = MsgBody(
|
||||
richText = MessageHelper.messageArrayToRichText(
|
||||
record.chatType,
|
||||
record.msgId,
|
||||
record.peerUin.toString(),
|
||||
record.elements.toSegments(
|
||||
record.chatType,
|
||||
record.peerUin.toString(),
|
||||
"0"
|
||||
).onEach { segment ->
|
||||
if (segment.type == "forward")
|
||||
forwardMsg[segment.data["filename"] as String] =
|
||||
segment.data["id"] as String
|
||||
}.toJson()
|
||||
).getOrElse { throw Exception("消息合成失败: $it") }.let {
|
||||
desc[++i] = record.sendMemberName.ifEmpty { record.sendNickName } + ": " + it.first
|
||||
it.second
|
||||
}
|
||||
)
|
||||
)
|
||||
} else if (data.containsKey("content")) {
|
||||
PushMsgBody(
|
||||
msgHead = ResponseHead(
|
||||
peer = data["uin"]?.asLong ?: TicketSvc.getUin().toLong(),
|
||||
peerUid = data["uid"]?.asString ?: TicketSvc.getUid(),
|
||||
receiverUid = TicketSvc.getUid(),
|
||||
forward = ResponseForward(
|
||||
friendName = data["name"]?.asStringOrNull ?: TicketSvc.getNickname()
|
||||
)
|
||||
),
|
||||
contentHead = ContentHead(
|
||||
msgType = 9,
|
||||
msgSubType = 175,
|
||||
divSeq = 175,
|
||||
msgViaRandom = Random.nextLong(),
|
||||
sequence = data["seq"]?.asLong ?: Random.nextLong(),
|
||||
msgTime = data["time"]?.asLong ?: (System.currentTimeMillis() / 1000),
|
||||
u2 = 1,
|
||||
u6 = 0,
|
||||
u7 = 0,
|
||||
msgSeq = data["seq"]?.asLong ?: Random.nextLong(),
|
||||
forwardHead = ForwardHead(
|
||||
u1 = 0,
|
||||
u2 = 0,
|
||||
u3 = 2,
|
||||
ub641 = "",
|
||||
avatar = ""
|
||||
)
|
||||
),
|
||||
body = MsgBody(
|
||||
richText = MessageHelper.messageArrayToRichText(
|
||||
chatType = chatType,
|
||||
msgId = Random.nextLong(),
|
||||
peerId = data["uin"]?.asString ?: TicketSvc.getUin(),
|
||||
messageList = when (data["content"]) {
|
||||
is JsonObject -> listOf(data["content"] as JsonObject).json
|
||||
is JsonArray -> data["content"] as JsonArray
|
||||
else -> MessageHelper.decodeCQCode(data["content"].asString)
|
||||
}.onEach { element ->
|
||||
val elementData = element.asJsonObject["data"].asJsonObject
|
||||
if (element.asJsonObject["type"].asString == "forward")
|
||||
forwardMsg[elementData["filename"].asString] =
|
||||
elementData["id"].asString
|
||||
}
|
||||
).getOrElse { throw Exception("消息合成失败: $it") }.let {
|
||||
desc[++i] =
|
||||
(data["name"].asStringOrNull ?: data["uin"].asStringOrNull
|
||||
?: TicketSvc.getNickname()) + ": " + it.first
|
||||
it.second
|
||||
}
|
||||
)
|
||||
)
|
||||
} else {
|
||||
error("消息节点缺少id或content字段")
|
||||
}
|
||||
}.getOrElse {
|
||||
LogCenter.log("消息节点解析失败:$it", Level.WARN)
|
||||
null
|
||||
}
|
||||
}.ifEmpty { return Result.failure(Exception("消息节点为空")) }
|
||||
|
||||
val payload = LongMsgPayload(
|
||||
action = listOf(
|
||||
action = mutableListOf(
|
||||
LongMsgAction(
|
||||
command = "MultiMsg",
|
||||
data = LongMsgContent(
|
||||
body = messages
|
||||
)
|
||||
body = msgs
|
||||
)
|
||||
)
|
||||
).apply {
|
||||
forwardMsg.map { msg ->
|
||||
addAll(getMultiMsg(msg.value).getOrElse { return Result.failure(Exception("无法获取嵌套转发消息: $it")) }
|
||||
.map { action ->
|
||||
if (action.command == "MultiMsg") LongMsgAction(
|
||||
command = msg.key,
|
||||
data = action.data
|
||||
) else action
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
LogCenter.log(payload.toByteArray().toHexString(), Level.DEBUG)
|
||||
|
||||
val req = LongMsgReq(
|
||||
sendInfo = SendLongMsgInfo(
|
||||
type = if (groupUin == null) 1 else 3,
|
||||
uid = LongMsgUid(groupUin ?: uid),
|
||||
groupUin = groupUin?.toInt(),
|
||||
sendInfo = when (chatType) {
|
||||
MsgConstant.KCHATTYPEC2C -> SendLongMsgInfo(
|
||||
type = 1,
|
||||
uid = LongMsgUid(peerId),
|
||||
payload = DeflateTools.gzip(payload.toByteArray())
|
||||
),
|
||||
)
|
||||
|
||||
MsgConstant.KCHATTYPEGROUP -> SendLongMsgInfo(
|
||||
type = 3,
|
||||
uid = LongMsgUid(fromId),
|
||||
groupUin = fromId.toInt(),
|
||||
payload = DeflateTools.gzip(payload.toByteArray())
|
||||
)
|
||||
|
||||
else -> throw UnsupportedOperationException(
|
||||
"Unsupported chatType: $chatType"
|
||||
)
|
||||
},
|
||||
setting = LongMsgSettings(
|
||||
field1 = 4,
|
||||
field2 = 2,
|
||||
@ -253,17 +407,29 @@ internal object MsgSvc : BaseSvc() {
|
||||
field4 = 0
|
||||
)
|
||||
)
|
||||
|
||||
val buffer = sendBufferAW(
|
||||
"trpc.group.long_msg_interface.MsgService.SsoSendLongMsg",
|
||||
true,
|
||||
req.toByteArray()
|
||||
) ?: return Result.failure(Exception("unable to upload multi message"))
|
||||
val rsp = buffer.slice(4).decodeProtobuf<LongMsgRsp>()
|
||||
return rsp.sendResult?.resId?.let { Result.success(it) }
|
||||
?: Result.failure(Exception("unable to upload multi message"))
|
||||
val resId = rsp.sendResult?.resId ?: return Result.failure(Exception("unable to upload multi message"))
|
||||
val filename = UUID.randomUUID().toString().uppercase()
|
||||
return Result.success(
|
||||
MessageSegment(
|
||||
"forward",
|
||||
mapOf(
|
||||
"id" to resId,
|
||||
"filename" to filename,
|
||||
"summary" to "查看${desc.size}条转发消息",
|
||||
"desc" to desc.slice(0..if (i < 3) i else 3).joinToString("\n")
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getMultiMsg(resId: String): Result<List<MessageDetail>> {
|
||||
suspend fun getMultiMsg(resId: String): Result<List<LongMsgAction>> {
|
||||
val req = LongMsgReq(
|
||||
recvInfo = RecvLongMsgInfo(
|
||||
uid = LongMsgUid(TicketSvc.getUid()),
|
||||
@ -284,11 +450,18 @@ internal object MsgSvc : BaseSvc() {
|
||||
) ?: return Result.failure(Exception("unable to get multi message"))
|
||||
val rsp = buffer.slice(4).decodeProtobuf<LongMsgRsp>()
|
||||
val zippedPayload = DeflateTools.ungzip(
|
||||
rsp.recvResult?.payload ?: return Result.failure(Exception("unable to get multi message"))
|
||||
rsp.recvResult?.payload ?: return Result.failure(Exception("payload is empty"))
|
||||
)
|
||||
LogCenter.log(zippedPayload.toHexString(), Level.DEBUG)
|
||||
val payload = zippedPayload.decodeProtobuf<LongMsgPayload>()
|
||||
payload.action?.forEach {
|
||||
return Result.success(
|
||||
zippedPayload.decodeProtobuf<LongMsgPayload>().action
|
||||
?: return Result.failure(Exception("action is empty"))
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getForwardMsg(resId: String): Result<List<MessageDetail>> {
|
||||
val result = getMultiMsg(resId).getOrElse { return Result.failure(it) }
|
||||
result.forEach {
|
||||
if (it.command == "MultiMsg") {
|
||||
return Result.success(it.data?.body?.map { msg ->
|
||||
val chatType =
|
||||
@ -296,27 +469,29 @@ internal object MsgSvc : BaseSvc() {
|
||||
MessageDetail(
|
||||
time = msg.contentHead?.msgTime?.toInt() ?: 0,
|
||||
msgType = MessageHelper.obtainDetailTypeByMsgType(chatType),
|
||||
msgId = 0, // MessageHelper.generateMsgIdHash(chatType, msg.content!!.msgViaRandom), msgViaRandom 为空
|
||||
msgId = 0, // msgViaRandom为空 tx不给
|
||||
qqMsgId = 0,
|
||||
msgSeq = msg.contentHead!!.msgSeq ?: 0,
|
||||
realId = msg.contentHead!!.msgSeq ?: 0,
|
||||
sender = MessageSender(
|
||||
msg.msgHead?.peer ?: 0,
|
||||
msg.msgHead?.responseGrp?.memberCard?.ifEmpty { msg.msgHead?.forward?.friendName }
|
||||
?: msg.msgHead?.forward?.friendName ?: "",
|
||||
msg.msgHead?.responseGrp?.memberCard ?: msg.msgHead?.forward?.friendName ?: "",
|
||||
"unknown",
|
||||
0,
|
||||
msg.msgHead?.peerUid ?: "",
|
||||
msg.msgHead?.peerUid ?: ""
|
||||
),
|
||||
message = msg.body?.richText?.elements?.toSegments(chatType, msg.msgHead?.peer.toString(), "0")
|
||||
?.toListMap() ?: emptyList(),
|
||||
message = msg.body?.richText?.toSegments(
|
||||
chatType,
|
||||
msg.msgHead?.peer.toString(),
|
||||
"0"
|
||||
)?.toListMap() ?: emptyList(),
|
||||
peerId = msg.msgHead?.peer ?: 0,
|
||||
groupId = if (chatType == MsgConstant.KCHATTYPEGROUP) msg.msgHead?.responseGrp?.groupCode?.toLong()
|
||||
?: 0 else 0,
|
||||
targetId = if (chatType != MsgConstant.KCHATTYPEGROUP) msg.msgHead?.peer ?: 0 else 0
|
||||
)
|
||||
}
|
||||
?: return Result.failure(Exception("Msg is empty")))
|
||||
} ?: return Result.failure(Exception("Msg is empty")))
|
||||
}
|
||||
}
|
||||
return Result.failure(Exception("Can't find msg"))
|
||||
|
@ -10,22 +10,16 @@ import io.ktor.utils.io.core.writeInt
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import moe.fuqiuluo.qqinterface.servlet.msg.MessageTempHandler
|
||||
|
||||
import moe.fuqiuluo.shamrock.remote.action.handlers.GetHistoryMsg
|
||||
import moe.fuqiuluo.shamrock.remote.service.listener.AioListener
|
||||
import moe.fuqiuluo.shamrock.tools.broadcast
|
||||
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
||||
import protobuf.message.element.LightAppElem
|
||||
import protobuf.message.PushMsgBody
|
||||
import protobuf.message.ContentHead
|
||||
import protobuf.message.Elem
|
||||
import protobuf.message.RichText
|
||||
import protobuf.message.ResponseHead
|
||||
import protobuf.message.MsgBody
|
||||
import protobuf.push.MessagePush
|
||||
import mqq.app.MobileQQ
|
||||
import protobuf.auto.toByteArray
|
||||
import protobuf.message.*
|
||||
import protobuf.message.element.LightAppElem
|
||||
import protobuf.push.MessagePush
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.text.toByteArray
|
||||
|
||||
internal object PacketSvc : BaseSvc() {
|
||||
/**
|
||||
@ -44,7 +38,12 @@ internal object PacketSvc: BaseSvc() {
|
||||
private suspend fun fakeReceiveSelfMsg(msgService: IKernelMsgService, builder: () -> List<Elem>): Long {
|
||||
val latestMsg = withTimeoutOrNull(3000) {
|
||||
suspendCancellableCoroutine {
|
||||
msgService.getMsgs(Contact(MsgConstant.KCHATTYPEC2C, app.currentUid, ""), 0L, 1, true) { code, why, msgs ->
|
||||
msgService.getMsgs(
|
||||
Contact(MsgConstant.KCHATTYPEC2C, app.currentUid, ""),
|
||||
0L,
|
||||
1,
|
||||
true
|
||||
) { code, why, msgs ->
|
||||
it.resume(GetHistoryMsg.GetMsgResult(code, why, msgs))
|
||||
}
|
||||
}
|
||||
@ -72,9 +71,11 @@ internal object PacketSvc: BaseSvc() {
|
||||
u4 = msgSeq - 2,
|
||||
u5 = msgSeq
|
||||
),
|
||||
body = MsgBody(RichText(
|
||||
body = MsgBody(
|
||||
RichText(
|
||||
elements = builder()
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -1,21 +1,46 @@
|
||||
package moe.fuqiuluo.qqinterface.servlet.msg
|
||||
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
||||
import moe.fuqiuluo.qqinterface.servlet.msg.converter.ElemConverter
|
||||
import moe.fuqiuluo.qqinterface.servlet.msg.converter.NtMsgElementConverter
|
||||
import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
||||
import moe.fuqiuluo.shamrock.tools.toHexString
|
||||
import protobuf.message.Elem
|
||||
import protobuf.message.RichText
|
||||
|
||||
@JvmName("elemListToSegments")
|
||||
internal suspend fun List<Elem>.toSegments(
|
||||
@JvmName("richTextToSegments")
|
||||
internal suspend fun RichText.toSegments(
|
||||
chatType: Int,
|
||||
peerId: String,
|
||||
subPeer: String
|
||||
): List<MessageSegment> {
|
||||
val messageData = arrayListOf<MessageSegment>()
|
||||
this.forEach { msg ->
|
||||
if (ptt != null) {
|
||||
val md5 = ptt!!.fileMd5!!
|
||||
messageData.add(
|
||||
MessageSegment(
|
||||
"record", mapOf(
|
||||
"file" to md5.toHexString(),
|
||||
"url" to when (chatType) {
|
||||
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPttDownUrl("0", ptt!!.fileUuid!!)
|
||||
MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupPttDownUrl(
|
||||
"0",
|
||||
md5,
|
||||
ptt!!.groupFileKey!!
|
||||
)
|
||||
|
||||
else -> throw UnsupportedOperationException("Not supported chat type: $chatType")
|
||||
},
|
||||
"magic" to ptt!!.pbReserve?.magic,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
elements?.forEach { msg ->
|
||||
kotlin.runCatching {
|
||||
val elementType = if (msg.text != null) {
|
||||
1
|
||||
|
@ -12,8 +12,8 @@ internal data class MessageSegment(
|
||||
) {
|
||||
fun toJson(): JsonObject {
|
||||
return mapOf(
|
||||
"type" to type.json,
|
||||
"data" to data.json
|
||||
"type" to type,
|
||||
"data" to data
|
||||
).json
|
||||
}
|
||||
}
|
||||
@ -29,6 +29,6 @@ internal fun List<MessageSegment>.toListMap(): List<Map<String, JsonElement>> {
|
||||
mapOf(
|
||||
"type" to it.type.json,
|
||||
"data" to it.data.json
|
||||
).json
|
||||
)
|
||||
}
|
||||
}
|
@ -13,11 +13,13 @@ import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.helper.db.ImageDB
|
||||
import moe.fuqiuluo.shamrock.helper.db.ImageMapping
|
||||
import moe.fuqiuluo.shamrock.helper.db.MessageDB
|
||||
import moe.fuqiuluo.shamrock.tools.asJsonArray
|
||||
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
||||
import moe.fuqiuluo.shamrock.tools.asJsonObject
|
||||
import moe.fuqiuluo.shamrock.tools.asString
|
||||
import moe.fuqiuluo.shamrock.tools.toHexString
|
||||
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import protobuf.message.Elem
|
||||
import protobuf.message.element.commelem.ButtonExtra
|
||||
import protobuf.message.element.commelem.MarkdownExtra
|
||||
@ -335,10 +337,8 @@ internal object ElemConverter {
|
||||
element: Elem
|
||||
): MessageSegment {
|
||||
val data = element.lightApp!!.data!!
|
||||
val jsonStr =
|
||||
(if (data[0].toInt() == 1) DeflateTools.uncompress(data.sliceArray(1 until data.size)) else data.sliceArray(
|
||||
1 until data.size
|
||||
)).toString()
|
||||
val jsonStr = String(if (data[0].toInt() == 1) DeflateTools.uncompress(data.slice(1)) else data.slice(1))
|
||||
LogCenter.log(jsonStr, Level.DEBUG)
|
||||
val json = jsonStr.asJsonObject
|
||||
return when (json["app"].asString) {
|
||||
"com.tencent.multimsg" -> {
|
||||
@ -346,7 +346,10 @@ internal object ElemConverter {
|
||||
MessageSegment(
|
||||
type = "forward",
|
||||
data = mapOf(
|
||||
"id" to info["resid"].asString
|
||||
"id" to info["resid"].asString,
|
||||
"filename" to info["uniseq"].asString,
|
||||
"summary" to info["summary"].asString,
|
||||
"desc" to info["news"].asJsonArray.joinToString("\n") { it.asJsonObject["text"].asString }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -2,10 +2,6 @@ package moe.fuqiuluo.qqinterface.servlet.msg.converter
|
||||
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
||||
import kotlinx.serialization.json.add
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.put
|
||||
import kotlinx.serialization.json.putJsonArray
|
||||
import moe.fuqiuluo.qqinterface.servlet.msg.MessageSegment
|
||||
import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc
|
||||
import moe.fuqiuluo.shamrock.helper.ContactHelper
|
||||
@ -15,6 +11,7 @@ import moe.fuqiuluo.shamrock.helper.MessageHelper
|
||||
import moe.fuqiuluo.shamrock.helper.db.ImageDB
|
||||
import moe.fuqiuluo.shamrock.helper.db.ImageMapping
|
||||
import moe.fuqiuluo.shamrock.helper.db.MessageDB
|
||||
import moe.fuqiuluo.shamrock.tools.asJsonArray
|
||||
import moe.fuqiuluo.shamrock.tools.asJsonObject
|
||||
import moe.fuqiuluo.shamrock.tools.asString
|
||||
import moe.fuqiuluo.shamrock.tools.hex2ByteArray
|
||||
@ -242,16 +239,10 @@ internal object NtMsgElementConverter {
|
||||
data = hashMapOf(
|
||||
"file" to md5,
|
||||
"url" to when (chatType) {
|
||||
MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPttDownUrl(
|
||||
"0",
|
||||
record.md5HexStr,
|
||||
record.fileUuid
|
||||
)
|
||||
|
||||
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPttDownUrl("0", record.fileUuid)
|
||||
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupPttDownUrl(
|
||||
MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupPttDownUrl(
|
||||
"0",
|
||||
record.md5HexStr,
|
||||
md5.hex2ByteArray(),
|
||||
record.fileUuid
|
||||
)
|
||||
|
||||
@ -343,7 +334,10 @@ internal object NtMsgElementConverter {
|
||||
MessageSegment(
|
||||
type = "forward",
|
||||
data = mapOf(
|
||||
"id" to info["resid"].asString
|
||||
"id" to info["resid"].asString,
|
||||
"filename" to info["uniseq"].asString,
|
||||
"summary" to info["summary"].asString,
|
||||
"desc" to info["news"].asJsonArray.joinToString("\n") { it.asJsonObject["text"].asString }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -13,23 +13,27 @@ import moe.fuqiuluo.qqinterface.servlet.msg.toJson
|
||||
import moe.fuqiuluo.qqinterface.servlet.msg.toSegments
|
||||
import moe.fuqiuluo.qqinterface.servlet.transfile.NtV2RichMediaSvc
|
||||
import moe.fuqiuluo.shamrock.helper.*
|
||||
import moe.fuqiuluo.shamrock.helper.MessageHelper.messageArrayToMessageElements
|
||||
import moe.fuqiuluo.shamrock.helper.MessageHelper.messageArrayToRichText
|
||||
import moe.fuqiuluo.shamrock.helper.MessageHelper.obtainMessageTypeByDetailType
|
||||
import moe.fuqiuluo.shamrock.tools.*
|
||||
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
||||
import moe.fuqiuluo.shamrock.utils.FileUtils
|
||||
import protobuf.auto.toByteArray
|
||||
import protobuf.message.Elem
|
||||
import protobuf.message.RichText
|
||||
import protobuf.message.element.*
|
||||
import protobuf.message.element.commelem.*
|
||||
import java.io.File
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
import kotlin.random.Random
|
||||
import kotlin.random.nextULong
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
internal typealias IElemMaker = suspend (Int, Long, String, JsonObject) -> Result<Elem>
|
||||
internal typealias IElemMaker = suspend (ElemMaker, Int, Long, String, JsonObject) -> Unit
|
||||
|
||||
internal object ElemMaker {
|
||||
internal class ElemMaker {
|
||||
companion object {
|
||||
private val makerArray = hashMapOf(
|
||||
"text" to ElemMaker::createTextElem,
|
||||
"at" to ElemMaker::createAtElem,
|
||||
@ -39,11 +43,13 @@ internal object ElemMaker {
|
||||
// "voice" to ElemMaker::createRecordElem,
|
||||
// "record" to ElemMaker::createRecordElem,
|
||||
// "video" to ElemMaker::createVideoElem,
|
||||
"markdown" to ElemMaker::createMarkdownElem,
|
||||
"button" to ElemMaker::createButtonElem,
|
||||
"forward" to ElemMaker::createForwardStruct,
|
||||
"json" to ElemMaker::createJsonElem,
|
||||
"poke" to ElemMaker::createPokeElem,
|
||||
"dice" to ElemMaker::createNewDiceElem,
|
||||
"rps" to ElemMaker::createNewRpsElem,
|
||||
"poke" to ElemMaker::createPokeElem,
|
||||
"markdown" to ElemMaker::createMarkdownElem,
|
||||
"button" to ElemMaker::createButtonElem,
|
||||
// "anonymous" to ElemMaker::createAnonymousElem,
|
||||
// "share" to ElemMaker::createShareElem,
|
||||
// "contact" to ElemMaker::createContactElem,
|
||||
@ -52,25 +58,39 @@ internal object ElemMaker {
|
||||
"reply" to ElemMaker::createReplyElem,
|
||||
// "touch" to ElemMaker::createTouchElem,
|
||||
"weather" to ElemMaker::createWeatherElem,
|
||||
"json" to ElemMaker::createJsonElem,
|
||||
//"forward" to MessageMaker::createForwardElem,
|
||||
//"multi_msg" to MessageMaker::createLongMsgStruct,
|
||||
//"bubble_face" to ElemMaker::createBubbleFaceElem,
|
||||
)
|
||||
|
||||
operator fun get(type: String): IElemMaker? = makerArray[type]
|
||||
}
|
||||
|
||||
private var rich = RichText()
|
||||
private val elems = mutableListOf<Elem>()
|
||||
private var desc = ""
|
||||
|
||||
fun getRich(): RichText {
|
||||
rich.elements = elems
|
||||
return rich
|
||||
}
|
||||
|
||||
fun getDesc(): String {
|
||||
return desc
|
||||
}
|
||||
|
||||
private suspend fun createTextElem(
|
||||
chatType: Int,
|
||||
msgId: Long,
|
||||
peerId: String,
|
||||
data: JsonObject
|
||||
): Result<Elem> {
|
||||
) {
|
||||
data.checkAndThrow("text")
|
||||
val elem = Elem(
|
||||
text = TextMsg(data["text"].asString)
|
||||
)
|
||||
return Result.success(elem)
|
||||
elems.add(elem)
|
||||
desc += data["text"].asString
|
||||
}
|
||||
|
||||
private suspend fun createAtElem(
|
||||
@ -78,8 +98,8 @@ internal object ElemMaker {
|
||||
msgId: Long,
|
||||
peerId: String,
|
||||
data: JsonObject
|
||||
): Result<Elem> {
|
||||
return when (chatType) {
|
||||
) {
|
||||
when (chatType) {
|
||||
MsgConstant.KCHATTYPEGROUP -> {
|
||||
data.checkAndThrow("qq")
|
||||
|
||||
@ -105,13 +125,12 @@ internal object ElemMaker {
|
||||
peerId.toLong(),
|
||||
qq,
|
||||
true
|
||||
)
|
||||
.let {
|
||||
).let {
|
||||
val info = it.getOrNull()
|
||||
if (info == null)
|
||||
LogCenter.log("无法获取群成员信息: $qqStr", Level.ERROR)
|
||||
info?.troopnick
|
||||
.ifNullOrEmpty(info?.friendnick)
|
||||
else info.troopnick
|
||||
.ifNullOrEmpty(info.friendnick)
|
||||
.ifNullOrEmpty(qqStr)
|
||||
})
|
||||
}
|
||||
@ -126,7 +145,8 @@ internal object ElemMaker {
|
||||
val elem = Elem(
|
||||
text = TextMsg(str = display, attr6Buf = attr6.array())
|
||||
)
|
||||
Result.success(elem)
|
||||
elems.add(elem)
|
||||
desc += display
|
||||
}
|
||||
|
||||
MsgConstant.KCHATTYPEC2C -> {
|
||||
@ -144,10 +164,11 @@ internal object ElemMaker {
|
||||
val elem = Elem(
|
||||
text = TextMsg(str = display)
|
||||
)
|
||||
Result.success(elem)
|
||||
elems.add(elem)
|
||||
desc += display
|
||||
}
|
||||
|
||||
else -> Result.failure(ActionMsgException)
|
||||
else -> throw UnsupportedOperationException("Unsupported chatType($chatType) for AtMsg")
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,7 +177,7 @@ internal object ElemMaker {
|
||||
msgId: Long,
|
||||
peerId: String,
|
||||
data: JsonObject
|
||||
): Result<Elem> {
|
||||
) {
|
||||
data.checkAndThrow("id")
|
||||
val faceId = data["id"].asInt
|
||||
val elem = if (data["big"].asBooleanOrNull == true) {
|
||||
@ -183,7 +204,8 @@ internal object ElemMaker {
|
||||
)
|
||||
)
|
||||
}
|
||||
return Result.success(elem)
|
||||
elems.add(elem)
|
||||
desc += "[表情]"
|
||||
}
|
||||
|
||||
private suspend fun createImageElem(
|
||||
@ -191,7 +213,7 @@ internal object ElemMaker {
|
||||
msgId: Long,
|
||||
peerId: String,
|
||||
data: JsonObject
|
||||
): Result<Elem> {
|
||||
) {
|
||||
val isOriginal = data["original"].asBooleanOrNull ?: true
|
||||
val isFlash = data["flash"].asBooleanOrNull ?: false
|
||||
val filePath = data["file"].asStringOrNull
|
||||
@ -307,7 +329,8 @@ internal object ElemMaker {
|
||||
|
||||
else -> throw LogicException("Not supported chatType($chatType) for PictureMsg")
|
||||
}
|
||||
return Result.success(elem)
|
||||
elems.add(elem)
|
||||
desc += "[图片]"
|
||||
}
|
||||
|
||||
private suspend fun createReplyElem(
|
||||
@ -315,16 +338,15 @@ internal object ElemMaker {
|
||||
msgId: Long,
|
||||
peerId: String,
|
||||
data: JsonObject
|
||||
): Result<Elem> {
|
||||
) {
|
||||
data.checkAndThrow("id")
|
||||
val msgHash = data["id"].asInt
|
||||
val mapping = MessageHelper.getMsgMappingByHash(msgHash)
|
||||
?: return Result.failure(Exception("不存在该消息映射,无法回复消息"))
|
||||
?: throw Exception("不存在该消息映射,无法回复消息")
|
||||
|
||||
if (mapping.qqMsgId == 0L) {
|
||||
// 貌似获取失败了,555
|
||||
LogCenter.log("无法获取被回复消息", Level.ERROR)
|
||||
return Result.failure(Exception("无法获取被回复消息"))
|
||||
throw Exception("无法获取被回复消息")
|
||||
}
|
||||
|
||||
val elem = if (data.containsKey("text")) {
|
||||
@ -351,16 +373,15 @@ internal object ElemMaker {
|
||||
)
|
||||
} else {
|
||||
val msg =
|
||||
MsgSvc.getMsgByQMsgId(chatType, mapping.peerId, mapping.qqMsgId).getOrNull() ?: return Result.failure(
|
||||
Exception("无法获取被回复消息")
|
||||
)
|
||||
MsgSvc.getMsgByQMsgId(chatType, mapping.peerId, mapping.qqMsgId).getOrNull()
|
||||
?: throw Exception("无法获取被回复消息")
|
||||
Elem(
|
||||
srcMsg = SourceMsg(
|
||||
origSeqs = listOf(msg.msgSeq.toInt()),
|
||||
senderUin = msg.senderUin.toULong(),
|
||||
time = msg.msgTime.toULong(),
|
||||
flag = 1u,
|
||||
elems = messageArrayToMessageElements(
|
||||
elems = messageArrayToRichText(
|
||||
msg.chatType,
|
||||
msg.msgId,
|
||||
msg.peerUin.toString(),
|
||||
@ -369,7 +390,7 @@ internal object ElemMaker {
|
||||
if (msg.chatType == MsgConstant.KCHATTYPEGUILD) msg.guildId else msg.peerUin.toString(),
|
||||
msg.channelId ?: msg.peerUin.toString()
|
||||
).toJson()
|
||||
).second,
|
||||
).getOrElse { throw Exception("解析回复消息失败: $it") }.second.elements,
|
||||
type = 0u,
|
||||
pbReserve = SourceMsg.Companion.PbReserve(
|
||||
msgRand = Random.nextULong(),
|
||||
@ -380,7 +401,8 @@ internal object ElemMaker {
|
||||
)
|
||||
)
|
||||
}
|
||||
return Result.success(elem)
|
||||
elems.add(elem)
|
||||
desc += "[回复消息]"
|
||||
}
|
||||
|
||||
private suspend fun createJsonElem(
|
||||
@ -388,15 +410,82 @@ internal object ElemMaker {
|
||||
msgId: Long,
|
||||
peerId: String,
|
||||
data: JsonObject
|
||||
): Result<Elem> {
|
||||
) {
|
||||
data.checkAndThrow("data")
|
||||
|
||||
val elem = Elem(
|
||||
lightApp = LightAppElem(
|
||||
data = DeflateTools.compress(data.toString().toByteArray())
|
||||
data = byteArrayOf(1) + DeflateTools.compress(data.toString().toByteArray())
|
||||
)
|
||||
)
|
||||
return Result.success(elem)
|
||||
elems.add(elem)
|
||||
desc += "[Json消息]"
|
||||
}
|
||||
|
||||
private suspend fun createForwardStruct(
|
||||
chatType: Int,
|
||||
msgId: Long,
|
||||
peerId: String,
|
||||
data: JsonObject
|
||||
) {
|
||||
data.checkAndThrow("id")
|
||||
val resId = data["id"].asString
|
||||
val filename = data["filename"].asStringOrNull ?: UUID.randomUUID().toString().uppercase()
|
||||
var summary = data["summary"].asStringOrNull
|
||||
val descriptions = data["desc"].asStringOrNull
|
||||
var news = descriptions?.split("\n")?.map { "text" to it }
|
||||
|
||||
if (news == null || summary == null) {
|
||||
val forwardMsg = MsgSvc.getForwardMsg(resId).getOrThrow()
|
||||
if (news == null) {
|
||||
news = forwardMsg.map {
|
||||
"text" to it.sender.nickName + ": " + messageArrayToRichText(
|
||||
obtainMessageTypeByDetailType(it.msgType),
|
||||
it.qqMsgId,
|
||||
it.peerId.toString(),
|
||||
it.message.json
|
||||
).getOrThrow().first
|
||||
}
|
||||
}
|
||||
if (summary == null) {
|
||||
summary = "查看${forwardMsg.size}条转发消息"
|
||||
}
|
||||
}
|
||||
|
||||
val json = mapOf(
|
||||
"app" to "com.tencent.multimsg",
|
||||
"config" to mapOf(
|
||||
"autosize" to 1,
|
||||
"forward" to 1,
|
||||
"round" to 1,
|
||||
"type" to "normal",
|
||||
"width" to 300
|
||||
),
|
||||
"desc" to "[聊天记录]",
|
||||
"extra" to mapOf(
|
||||
"filename" to filename,
|
||||
"tsum" to 2
|
||||
).json.toString(),
|
||||
"meta" to mapOf(
|
||||
"detail" to mapOf(
|
||||
"news" to news,
|
||||
"resid" to resId,
|
||||
"source" to "群聊的聊天记录",
|
||||
"summary" to summary,
|
||||
"uniseq" to filename
|
||||
)
|
||||
),
|
||||
"prompt" to "[聊天记录]",
|
||||
"ver" to "0.0.0.5",
|
||||
"view" to "contact"
|
||||
)
|
||||
val elem = Elem(
|
||||
lightApp = LightAppElem(
|
||||
data = byteArrayOf(1) + DeflateTools.compress(json.json.toString().toByteArray())
|
||||
)
|
||||
)
|
||||
elems.add(elem)
|
||||
desc += "[聊天记录]"
|
||||
}
|
||||
|
||||
private suspend fun createWeatherElem(
|
||||
@ -404,7 +493,7 @@ internal object ElemMaker {
|
||||
msgId: Long,
|
||||
peerId: String,
|
||||
data: JsonObject
|
||||
): Result<Elem> {
|
||||
) {
|
||||
var code = data["code"].asIntOrNull
|
||||
|
||||
if (code == null) {
|
||||
@ -416,27 +505,30 @@ internal object ElemMaker {
|
||||
}
|
||||
|
||||
if (code != null) {
|
||||
WeatherSvc.fetchWeatherCard(code).onSuccess {
|
||||
val weatherCard = WeatherSvc.fetchWeatherCard(code).getOrThrow()
|
||||
// OidbSvc.0xdc2_34
|
||||
// 00 00 00 DF 08 C2 1B 10 22 22 C4 01 0A B7 01 08 A2 E0 F2 2F 10 01 18 00 2A 02 08 01 58 FB 91 F6 AE 02 62 A1 01 08 01 52 08 E5 8C 97 E4 BA AC 20 20 5A 19 2D 33 C2 B0 2F 33 C2 B0 0A E7 A9 BA E6 B0 94 E8 B4 A8 E9 87 8F 3A E8 89 AF 62 11 5B E5 88 86 E4 BA AB 5D 20 E5 8C 97 E4 BA AC 20 20 6A 25 68 74 74 70 73 3A 2F 2F 77 65 61 74 68 65 72 2E 6D 70 2E 71 71 2E 63 6F 6D 2F 3F 73 74 3D 30 26 5F 77 76 3D 31 72 3E 68 74 74 70 73 3A 2F 2F 69 6D 67 63 61 63 68 65 2E 71 71 2E 63 6F 6D 2F 61 63 2F 71 71 77 65 61 74 68 65 72 2F 69 6D 61 67 65 2F 73 68 61 72 65 5F 69 63 6F 6E 2F 66 69 6E 65 2E 70 6E 67 12 08 08 01 10 FB 91 F6 AE 02 32 0D 61 6E 64 72 6F 69 64 20 39 2E 30 2E 38
|
||||
return createJsonElem(
|
||||
chatType, msgId, peerId, it["weekStore"]
|
||||
.asJsonObject["share"].asJsonObject
|
||||
val elem = Elem(
|
||||
lightApp = LightAppElem(
|
||||
data = byteArrayOf(1) + DeflateTools.compress(
|
||||
weatherCard["weekStore"]
|
||||
.asJsonObject["share"].asString.toByteArray()
|
||||
)
|
||||
}.onFailure {
|
||||
LogCenter.log("无法发送天气分享", Level.ERROR)
|
||||
)
|
||||
)
|
||||
elems.add(elem)
|
||||
desc += "[天气卡片]"
|
||||
} else {
|
||||
throw LogicException("无法获取城市天气")
|
||||
}
|
||||
}
|
||||
|
||||
return Result.failure(ActionMsgException)
|
||||
}
|
||||
|
||||
private suspend fun createPokeElem(
|
||||
chatType: Int,
|
||||
msgId: Long,
|
||||
peerId: String,
|
||||
data: JsonObject
|
||||
): Result<Elem> {
|
||||
) {
|
||||
data.checkAndThrow("type", "id")
|
||||
val elem = Elem(
|
||||
commonElem = CommonElem(
|
||||
@ -449,7 +541,8 @@ internal object ElemMaker {
|
||||
businessType = data["id"].asInt
|
||||
)
|
||||
)
|
||||
return Result.success(elem)
|
||||
elems.add(elem)
|
||||
desc += "[戳一戳]"
|
||||
}
|
||||
|
||||
private suspend fun createNewDiceElem(
|
||||
@ -457,7 +550,7 @@ internal object ElemMaker {
|
||||
msgId: Long,
|
||||
peerId: String,
|
||||
data: JsonObject
|
||||
): Result<Elem> {
|
||||
) {
|
||||
val elem = Elem(
|
||||
commonElem = CommonElem(
|
||||
serviceType = 37,
|
||||
@ -474,7 +567,8 @@ internal object ElemMaker {
|
||||
businessType = 2
|
||||
)
|
||||
)
|
||||
return Result.success(elem)
|
||||
elems.add(elem)
|
||||
desc += "[骰子]"
|
||||
}
|
||||
|
||||
private suspend fun createNewRpsElem(
|
||||
@ -482,7 +576,7 @@ internal object ElemMaker {
|
||||
msgId: Long,
|
||||
peerId: String,
|
||||
data: JsonObject
|
||||
): Result<Elem> {
|
||||
) {
|
||||
val elem = Elem(
|
||||
commonElem = CommonElem(
|
||||
serviceType = 37,
|
||||
@ -499,7 +593,8 @@ internal object ElemMaker {
|
||||
businessType = 1
|
||||
)
|
||||
)
|
||||
return Result.success(elem)
|
||||
elems.add(elem)
|
||||
desc += "[包剪锤]"
|
||||
}
|
||||
|
||||
private suspend fun createMarkdownElem(
|
||||
@ -507,7 +602,7 @@ internal object ElemMaker {
|
||||
msgId: Long,
|
||||
peerId: String,
|
||||
data: JsonObject
|
||||
): Result<Elem> {
|
||||
) {
|
||||
data.checkAndThrow("content")
|
||||
val elem = Elem(
|
||||
commonElem = CommonElem(
|
||||
@ -516,7 +611,8 @@ internal object ElemMaker {
|
||||
businessType = 1
|
||||
)
|
||||
)
|
||||
return Result.success(elem)
|
||||
elems.add(elem)
|
||||
desc += "[Markdown消息]"
|
||||
}
|
||||
|
||||
private suspend fun createButtonElem(
|
||||
@ -524,7 +620,7 @@ internal object ElemMaker {
|
||||
msgId: Long,
|
||||
peerId: String,
|
||||
data: JsonObject
|
||||
): Result<Elem> {
|
||||
) {
|
||||
data.checkAndThrow("buttons")
|
||||
val elem = Elem(
|
||||
commonElem = CommonElem(
|
||||
@ -565,7 +661,8 @@ internal object ElemMaker {
|
||||
businessType = 1
|
||||
)
|
||||
)
|
||||
return Result.success(elem)
|
||||
elems.add(elem)
|
||||
desc += "[Button消息]"
|
||||
}
|
||||
|
||||
private fun JsonObject.checkAndThrow(vararg key: String) {
|
||||
|
@ -16,6 +16,7 @@ import kotlinx.serialization.json.JsonPrimitive
|
||||
import moe.fuqiuluo.qqinterface.servlet.CardSvc
|
||||
import moe.fuqiuluo.qqinterface.servlet.GroupSvc
|
||||
import moe.fuqiuluo.qqinterface.servlet.LbsSvc
|
||||
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
||||
import moe.fuqiuluo.qqinterface.servlet.ark.data.ArkAppInfo
|
||||
import moe.fuqiuluo.qqinterface.servlet.ark.ArkMsgSvc
|
||||
import moe.fuqiuluo.qqinterface.servlet.ark.WeatherSvc
|
||||
@ -51,6 +52,8 @@ import tencent.im.oidb.cmd0xb77.oidb_cmd0xb77
|
||||
import tencent.im.oidb.cmd0xdc2.oidb_cmd0xdc2
|
||||
import tencent.im.oidb.oidb_sso
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.random.Random
|
||||
import kotlin.random.nextInt
|
||||
@ -80,10 +83,11 @@ internal object NtMsgElementMaker {
|
||||
"touch" to NtMsgElementMaker::createTouchElem,
|
||||
"weather" to NtMsgElementMaker::createWeatherElem,
|
||||
"json" to NtMsgElementMaker::createJsonElem,
|
||||
"forward" to NtMsgElementMaker::createForwardStruct,
|
||||
"new_dice" to NtMsgElementMaker::createNewDiceElem,
|
||||
"new_rps" to NtMsgElementMaker::createNewRpsElem,
|
||||
"basketball" to NtMsgElementMaker::createBasketballElem,
|
||||
//"multi_msg" to MessageMaker::createLongMsgStruct,
|
||||
//"multi_msg" to NtMsgElementMaker::createLongMsgStruct,
|
||||
"bubble_face" to NtMsgElementMaker::createBubbleFaceElem,
|
||||
"button" to NtMsgElementMaker::createInlineKeywordElem,
|
||||
"inline_keyboard" to NtMsgElementMaker::createInlineKeywordElem
|
||||
@ -91,6 +95,70 @@ internal object NtMsgElementMaker {
|
||||
|
||||
operator fun get(type: String): IMsgElementMaker? = makerMap[type]
|
||||
|
||||
private suspend fun createForwardStruct(
|
||||
chatType: Int,
|
||||
msgId: Long,
|
||||
peerId: String,
|
||||
data: JsonObject
|
||||
): Result<MsgElement> {
|
||||
data.checkAndThrow("id")
|
||||
val resId = data["id"].asString
|
||||
val filename = data["filename"].asStringOrNull ?: UUID.randomUUID().toString().uppercase()
|
||||
var summary = data["summary"].asStringOrNull
|
||||
val descriptions = data["desc"].asStringOrNull
|
||||
var news = descriptions?.split("\n")?.map { "text" to it }
|
||||
|
||||
if (news == null || summary == null) {
|
||||
val forwardMsg = MsgSvc.getForwardMsg(resId).getOrElse { return Result.failure(it) }
|
||||
if (news == null) {
|
||||
news = forwardMsg.map {
|
||||
"text" to it.sender.nickName + ": " + MessageHelper.messageArrayToRichText(
|
||||
MessageHelper.obtainMessageTypeByDetailType(it.msgType),
|
||||
it.qqMsgId,
|
||||
it.peerId.toString(),
|
||||
it.message.json
|
||||
).getOrThrow().first
|
||||
}
|
||||
}
|
||||
if (summary == null) {
|
||||
summary = "查看${forwardMsg.size}条转发消息"
|
||||
}
|
||||
}
|
||||
|
||||
val json = mapOf(
|
||||
"app" to "com.tencent.multimsg",
|
||||
"config" to mapOf(
|
||||
"autosize" to 1,
|
||||
"forward" to 1,
|
||||
"round" to 1,
|
||||
"type" to "normal",
|
||||
"width" to 300
|
||||
),
|
||||
"desc" to "[聊天记录]",
|
||||
"extra" to mapOf(
|
||||
"filename" to filename,
|
||||
"tsum" to 2
|
||||
).json.toString(),
|
||||
"meta" to mapOf(
|
||||
"detail" to mapOf(
|
||||
"news" to news,
|
||||
"resid" to resId,
|
||||
"source" to "群聊的聊天记录",
|
||||
"summary" to summary,
|
||||
"uniseq" to filename
|
||||
)
|
||||
),
|
||||
"prompt" to "[聊天记录]",
|
||||
"ver" to "0.0.0.5",
|
||||
"view" to "contact"
|
||||
)
|
||||
return createJsonElem(
|
||||
chatType, msgId, peerId, mapOf(
|
||||
"data" to json
|
||||
).json
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun createInlineKeywordElem(
|
||||
chatType: Int,
|
||||
msgId: Long,
|
||||
@ -262,19 +330,21 @@ internal object NtMsgElementMaker {
|
||||
): Result<MsgElement> {
|
||||
data.checkAndThrow("data")
|
||||
val jsonStr = data["data"].let {
|
||||
if (it is JsonObject) it.asJsonObject.toString() else {
|
||||
val str = it.asStringOrNull ?: ""
|
||||
if (it is JsonObject) {
|
||||
it.asJsonObject.toString()
|
||||
} else {
|
||||
// 检查字符串是否是合法json,不然qq会闪退
|
||||
try {
|
||||
val str = it.asString
|
||||
val element = Json.decodeFromString<JsonElement>(str)
|
||||
if (element !is JsonObject) {
|
||||
return Result.failure(Exception("不合法的JSON字符串"))
|
||||
}
|
||||
str
|
||||
} catch (err: Throwable) {
|
||||
LogCenter.log(err.stackTraceToString(), Level.ERROR)
|
||||
return Result.failure(Exception("不合法的JSON字符串"))
|
||||
}
|
||||
str
|
||||
}
|
||||
}
|
||||
val element = MsgElement()
|
||||
|
@ -6,7 +6,6 @@ import com.tencent.mobileqq.transfile.FileMsg
|
||||
import com.tencent.mobileqq.transfile.api.IProtoReqManager
|
||||
import com.tencent.mobileqq.transfile.protohandler.RichProto
|
||||
import com.tencent.mobileqq.transfile.protohandler.RichProtoProc
|
||||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
||||
@ -22,23 +21,9 @@ import moe.fuqiuluo.shamrock.xposed.helper.AppRuntimeFetcher
|
||||
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||
import mqq.app.MobileQQ
|
||||
import protobuf.auto.toByteArray
|
||||
import protobuf.oidb.TrpcOidb
|
||||
import protobuf.oidb.cmd0x11c5.C2CUserInfo
|
||||
import protobuf.oidb.cmd0x11c5.ChannelUserInfo
|
||||
import protobuf.oidb.cmd0x11c5.ClientMeta
|
||||
import protobuf.oidb.cmd0x11c5.CodecConfigReq
|
||||
import protobuf.oidb.cmd0x11c5.CommonHead
|
||||
import protobuf.oidb.cmd0x11c5.DownloadExt
|
||||
import protobuf.oidb.cmd0x11c5.DownloadReq
|
||||
import protobuf.oidb.cmd0x11c5.FileInfo
|
||||
import protobuf.oidb.cmd0x11c5.FileType
|
||||
import protobuf.oidb.cmd0x11c5.GroupUserInfo
|
||||
import protobuf.oidb.cmd0x11c5.IndexNode
|
||||
import protobuf.oidb.cmd0x11c5.MultiMediaReqHead
|
||||
import protobuf.oidb.cmd0x11c5.NtV2RichMediaReq
|
||||
import protobuf.oidb.cmd0x11c5.NtV2RichMediaRsp
|
||||
import protobuf.oidb.cmd0x11c5.SceneInfo
|
||||
import protobuf.oidb.cmd0x11c5.VideoDownloadExt
|
||||
import protobuf.oidb.cmd0xfc2.Oidb0xfc2ChannelInfo
|
||||
import protobuf.oidb.cmd0xfc2.Oidb0xfc2MsgApplyDownloadReq
|
||||
import protobuf.oidb.cmd0xfc2.Oidb0xfc2ReqBody
|
||||
@ -405,8 +390,8 @@ internal object RichProtoSvc: BaseSvc() {
|
||||
|
||||
suspend fun getGroupPttDownUrl(
|
||||
peerId: String,
|
||||
md5Hex: String,
|
||||
fileUUId: String
|
||||
md5: ByteArray,
|
||||
groupFileKey: String
|
||||
): String {
|
||||
return suspendCancellableCoroutine {
|
||||
val runtime = AppRuntimeFetcher.appRuntime
|
||||
@ -417,8 +402,8 @@ internal object RichProtoSvc: BaseSvc() {
|
||||
groupPttDownReq.secondUin = peerId
|
||||
groupPttDownReq.uinType = FileMsg.UIN_TROOP
|
||||
groupPttDownReq.groupFileID = 0
|
||||
groupPttDownReq.groupFileKey = fileUUId
|
||||
groupPttDownReq.md5 = md5Hex.hex2ByteArray()
|
||||
groupPttDownReq.groupFileKey = groupFileKey
|
||||
groupPttDownReq.md5 = md5
|
||||
groupPttDownReq.voiceType = 1
|
||||
groupPttDownReq.downType = 1
|
||||
richProtoReq.callback = RichProtoProc.RichProtoCallback { _, resp ->
|
||||
|
@ -21,12 +21,9 @@ import moe.fuqiuluo.qqinterface.servlet.msg.maker.NtMsgElementMaker
|
||||
import moe.fuqiuluo.shamrock.helper.db.MessageDB
|
||||
import moe.fuqiuluo.shamrock.helper.db.MessageMapping
|
||||
import moe.fuqiuluo.shamrock.remote.structures.SendMsgResult
|
||||
import moe.fuqiuluo.shamrock.tools.EmptyJsonObject
|
||||
import moe.fuqiuluo.shamrock.tools.asJsonObjectOrNull
|
||||
import moe.fuqiuluo.shamrock.tools.asString
|
||||
import moe.fuqiuluo.shamrock.tools.json
|
||||
import moe.fuqiuluo.shamrock.tools.jsonArray
|
||||
import moe.fuqiuluo.shamrock.tools.*
|
||||
import protobuf.message.Elem
|
||||
import protobuf.message.RichText
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.math.abs
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
@ -319,38 +316,30 @@ internal object MessageHelper {
|
||||
return hasActionMsg to msgList
|
||||
}
|
||||
|
||||
suspend fun messageArrayToMessageElements(
|
||||
suspend fun messageArrayToRichText(
|
||||
chatType: Int,
|
||||
msgId: Long,
|
||||
peerId: String,
|
||||
messageList: JsonArray
|
||||
): Pair<Boolean, ArrayList<Elem>> {
|
||||
val msgList = arrayListOf<Elem>()
|
||||
var hasActionMsg = false
|
||||
messageList.forEach {
|
||||
val msg = it.jsonObject
|
||||
): Result<Pair<String, RichText>> {
|
||||
val elemMaker = ElemMaker()
|
||||
messageList.forEach { element ->
|
||||
val msg = element.asJsonObject
|
||||
val maker = ElemMaker[msg["type"].asString]
|
||||
if (maker != null) {
|
||||
try {
|
||||
val data = msg["data"].asJsonObjectOrNull ?: EmptyJsonObject
|
||||
maker(chatType, msgId, peerId, data).onSuccess { msgElem ->
|
||||
msgList.add(msgElem)
|
||||
}.onFailure {
|
||||
if (it.javaClass != ActionMsgException::class.java) {
|
||||
throw it
|
||||
} else {
|
||||
hasActionMsg = true
|
||||
}
|
||||
}
|
||||
maker(elemMaker, chatType, msgId, peerId, data)
|
||||
} catch (e: Throwable) {
|
||||
if (e.javaClass != ActionMsgException::class.java) {
|
||||
LogCenter.log(e.stackTraceToString(), Level.ERROR)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LogCenter.log("不支持的消息类型: ${msg["type"].asString}", Level.ERROR)
|
||||
return false to arrayListOf()
|
||||
}
|
||||
}
|
||||
return hasActionMsg to msgList
|
||||
return Result.success(elemMaker.getDesc() to elemMaker.getRich())
|
||||
}
|
||||
|
||||
fun generateMsgIdHash(chatType: Int, msgId: Long): Int {
|
||||
|
@ -1,12 +1,10 @@
|
||||
package moe.fuqiuluo.shamrock.remote.action.handlers
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
||||
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
||||
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
|
||||
import moe.fuqiuluo.shamrock.remote.service.data.MessageDetail
|
||||
import moe.fuqiuluo.shamrock.remote.service.data.GetForwardMsgResult
|
||||
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
|
||||
import moe.fuqiuluo.symbols.OneBotHandler
|
||||
|
||||
@ -21,14 +19,12 @@ internal object GetForwardMsg : IActionHandler() {
|
||||
resId: String,
|
||||
echo: JsonElement = EmptyJsonString
|
||||
): String {
|
||||
val result = MsgSvc.getMultiMsg(resId).getOrElse { return logic(it.toString(), echo) }
|
||||
return ok(data = GetForwardMsgResult(result), echo = echo)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class GetForwardMsgResult(
|
||||
@SerialName("messages") val msgs: List<MessageDetail>
|
||||
return ok(
|
||||
data = GetForwardMsgResult(
|
||||
msgs = MsgSvc.getForwardMsg(resId).getOrElse { return logic(it.toString(), echo = echo) }),
|
||||
echo = echo
|
||||
)
|
||||
}
|
||||
|
||||
override val requiredParams: Array<String> = arrayOf("id")
|
||||
}
|
@ -69,6 +69,7 @@ internal object GetHistoryMsg : IActionHandler() {
|
||||
time = msg.msgTime.toInt(),
|
||||
msgType = MessageHelper.obtainDetailTypeByMsgType(msg.chatType),
|
||||
msgId = msgHash,
|
||||
qqMsgId = msg.msgId,
|
||||
msgSeq = msg.msgSeq,
|
||||
realId = msg.msgSeq,
|
||||
sender = MessageSender(
|
||||
@ -92,6 +93,7 @@ internal object GetHistoryMsg : IActionHandler() {
|
||||
time = msg.msgTime.toInt(),
|
||||
msgType = MessageHelper.obtainDetailTypeByMsgType(msg.chatType),
|
||||
msgId = MessageHelper.generateMsgIdHash(msg.chatType, msg.msgId),
|
||||
qqMsgId = msg.msgId,
|
||||
msgSeq = msg.msgSeq,
|
||||
realId = msg.msgSeq,
|
||||
sender = MessageSender(
|
||||
|
@ -29,6 +29,7 @@ internal object GetMsg: IActionHandler() {
|
||||
time = msg.msgTime.toInt(),
|
||||
msgType = MessageHelper.obtainDetailTypeByMsgType(msg.chatType),
|
||||
msgId = msgHash,
|
||||
qqMsgId = msg.msgId,
|
||||
msgSeq = msg.msgSeq,
|
||||
realId = msg.msgSeq,
|
||||
sender = MessageSender(
|
||||
|
@ -3,20 +3,14 @@ package moe.fuqiuluo.shamrock.remote.action.handlers
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||
import kotlinx.serialization.json.*
|
||||
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
||||
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
||||
import moe.fuqiuluo.qqinterface.servlet.msg.toSegments
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.qqinterface.servlet.msg.toJson
|
||||
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
||||
import moe.fuqiuluo.shamrock.helper.ParamsException
|
||||
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.remote.service.data.SendForwardMessageResult
|
||||
import moe.fuqiuluo.shamrock.tools.*
|
||||
import moe.fuqiuluo.symbols.OneBotHandler
|
||||
import protobuf.message.*
|
||||
import java.util.*
|
||||
import kotlin.random.Random
|
||||
|
||||
@OneBotHandler("send_forward_msg")
|
||||
internal object SendForwardMessage : IActionHandler() {
|
||||
@ -60,9 +54,10 @@ internal object SendForwardMessage : IActionHandler() {
|
||||
|
||||
else -> error("unknown chat type: $chatType")
|
||||
}.toString()
|
||||
val retryCnt = session.getIntOrNull("retry_cnt") ?: 5
|
||||
return if (session.isArray("messages")) {
|
||||
val messages = session.getArray("messages")
|
||||
invoke(chatType, peerId, messages, fromId, echo = session.echo)
|
||||
invoke(chatType, peerId, fromId, messages, retryCnt, session.echo)
|
||||
} else {
|
||||
logic("未知格式合并转发消息", session.echo)
|
||||
}
|
||||
@ -76,222 +71,22 @@ internal object SendForwardMessage : IActionHandler() {
|
||||
suspend operator fun invoke(
|
||||
chatType: Int,
|
||||
peerId: String,
|
||||
messages: JsonArray,
|
||||
fromId: String = peerId,
|
||||
messages: JsonArray,
|
||||
retryCnt: Int,
|
||||
echo: JsonElement = EmptyJsonString
|
||||
): String {
|
||||
var uid: String? = null
|
||||
var groupUin: String? = null
|
||||
|
||||
var i = -1
|
||||
val desc = MutableList(messages.size) { "" }
|
||||
|
||||
val msgs = messages.map { msg ->
|
||||
kotlin.runCatching {
|
||||
val data = msg.asJsonObject["data"].asJsonObject
|
||||
if (data.containsKey("id")) {
|
||||
val record = MsgSvc.getMsg(data["id"].asInt).getOrElse {
|
||||
error("合并转发消息节点消息(id = ${data["id"].asInt})获取失败:$it")
|
||||
}
|
||||
if (record.chatType == MsgConstant.KCHATTYPEGROUP) groupUin = record.peerUin.toString()
|
||||
if (record.chatType == MsgConstant.KCHATTYPEC2C) uid = record.peerUid
|
||||
PushMsgBody(
|
||||
msgHead = ResponseHead(
|
||||
peerUid = record.senderUid,
|
||||
receiverUid = record.peerUid,
|
||||
forward = ResponseForward(
|
||||
friendName = record.sendNickName
|
||||
),
|
||||
responseGrp = if (record.chatType == MsgConstant.KCHATTYPEGROUP) ResponseGrp(
|
||||
groupCode = record.peerUin.toULong(),
|
||||
memberCard = record.sendMemberName,
|
||||
u1 = 2
|
||||
) else null
|
||||
),
|
||||
contentHead = ContentHead(
|
||||
msgType = when (record.chatType) {
|
||||
MsgConstant.KCHATTYPEC2C -> 9
|
||||
MsgConstant.KCHATTYPEGROUP -> 82
|
||||
else -> throw UnsupportedOperationException(
|
||||
"Unsupported chatType: $chatType"
|
||||
)
|
||||
},
|
||||
msgSubType = if (record.chatType == MsgConstant.KCHATTYPEC2C) 175 else null,
|
||||
divSeq = if (record.chatType == MsgConstant.KCHATTYPEC2C) 175 else null,
|
||||
msgViaRandom = record.msgId,
|
||||
sequence = record.msgSeq, // idk what this is(i++)
|
||||
msgTime = record.msgTime,
|
||||
u2 = 1,
|
||||
u6 = 0,
|
||||
u7 = 0,
|
||||
msgSeq = if (record.chatType == MsgConstant.KCHATTYPEC2C) record.msgSeq else null, // seq for dm
|
||||
forwardHead = ForwardHead(
|
||||
u1 = 0,
|
||||
u2 = 0,
|
||||
u3 = 0,
|
||||
ub641 = "",
|
||||
avatar = ""
|
||||
)
|
||||
),
|
||||
body = MsgBody(
|
||||
richText = RichText(
|
||||
elements = MessageHelper.messageArrayToMessageElements(
|
||||
record.chatType,
|
||||
record.msgId,
|
||||
record.peerUin.toString(),
|
||||
record.elements.toSegments(
|
||||
record.chatType,
|
||||
record.peerUin.toString(),
|
||||
"0"
|
||||
).also {
|
||||
desc[++i] = record.sendMemberName.ifEmpty { record.sendNickName } + ": "
|
||||
}.map {
|
||||
desc[i] += when (it.type) {
|
||||
"text" -> it.data["text"] as String
|
||||
"at" -> "@${it.data["name"] as String? ?: it.data["qq"] as String}"
|
||||
"face" -> "[表情]"
|
||||
"pic", "image" -> "[图片]"
|
||||
"voice", "record" -> "[语音]"
|
||||
"video" -> "[视频]"
|
||||
"node" -> "[合并转发消息]"
|
||||
"markdown" -> "[Markdown消息]"
|
||||
"button" -> "[Button类型]"
|
||||
else -> "[未知消息类型]"
|
||||
}
|
||||
it.toJson()
|
||||
}.json
|
||||
).also {
|
||||
if (it.second.isEmpty() && !it.first)
|
||||
error("消息合成失败,请查看日志或者检查输入。")
|
||||
}.second
|
||||
)
|
||||
)
|
||||
)
|
||||
} else if (data.containsKey("content")) {
|
||||
PushMsgBody(
|
||||
msgHead = ResponseHead(
|
||||
peer = data["uin"]?.asLong ?: TicketSvc.getUin().toLong(),
|
||||
peerUid = data["uid"]?.asString ?: TicketSvc.getUid(),
|
||||
receiverUid = TicketSvc.getUid(),
|
||||
forward = ResponseForward(
|
||||
friendName = data["name"]?.asStringOrNull ?: TicketSvc.getNickname()
|
||||
)
|
||||
),
|
||||
contentHead = ContentHead(
|
||||
msgType = 9,
|
||||
msgSubType = 175,
|
||||
divSeq = 175,
|
||||
msgViaRandom = Random.nextLong(),
|
||||
sequence = data["seq"]?.asLong ?: Random.nextLong(),
|
||||
msgTime = data["time"]?.asLong ?: (System.currentTimeMillis() / 1000),
|
||||
u2 = 1,
|
||||
u6 = 0,
|
||||
u7 = 0,
|
||||
msgSeq = data["seq"]?.asLong ?: Random.nextLong(),
|
||||
forwardHead = ForwardHead(
|
||||
u1 = 0,
|
||||
u2 = 0,
|
||||
u3 = 2,
|
||||
ub641 = "",
|
||||
avatar = ""
|
||||
)
|
||||
),
|
||||
body = MsgBody(
|
||||
richText = RichText(
|
||||
elements = MessageHelper.messageArrayToMessageElements(
|
||||
chatType = chatType,
|
||||
msgId = Random.nextLong(),
|
||||
peerId = data["uin"]?.asString ?: TicketSvc.getUin(),
|
||||
messageList = 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 {
|
||||
val type = it.asJsonObject["type"].asString
|
||||
val itData = it.asJsonObject["data"].asJsonObject
|
||||
desc[i] += when (type) {
|
||||
"text" -> itData["text"].asString
|
||||
"at" -> "@${itData["name"].asStringOrNull ?: itData["qq"].asString}"
|
||||
"face" -> "[表情]"
|
||||
"pic", "image" -> "[图片]"
|
||||
"voice", "record" -> "[语音]"
|
||||
"video" -> "[视频]"
|
||||
"node" -> "[合并转发消息]"
|
||||
"markdown" -> "[Markdown消息]"
|
||||
"button" -> "[Button类型]"
|
||||
else -> "[未知消息类型]"
|
||||
}
|
||||
}
|
||||
).also {
|
||||
if (it.second.isEmpty() && !it.first)
|
||||
error("消息合成失败,请查看日志或者检查输入。")
|
||||
}.second
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
error("消息节点缺少id或content字段")
|
||||
}
|
||||
}.getOrElse {
|
||||
LogCenter.log("消息节点解析失败:$it", Level.WARN)
|
||||
null
|
||||
}
|
||||
}.filterNotNull().ifEmpty { return logic("消息节点为空", echo) }
|
||||
|
||||
|
||||
kotlin.runCatching {
|
||||
val resid = MsgSvc.uploadMultiMsg(uid ?: TicketSvc.getUid(), groupUin, msgs)
|
||||
val message = MsgSvc.uploadMultiMsg(chatType, peerId, fromId, messages, retryCnt)
|
||||
.getOrElse { return logic(it.message ?: "", echo) }
|
||||
val uniseq = UUID.randomUUID().toString().uppercase()
|
||||
|
||||
val result = MsgSvc.sendToAio(
|
||||
chatType, peerId,
|
||||
listOf(
|
||||
mapOf(
|
||||
"type" to "json",
|
||||
"data" to mapOf(
|
||||
"data" to mapOf(
|
||||
"app" to "com.tencent.multimsg",
|
||||
"config" to mapOf(
|
||||
"autosize" to 1,
|
||||
"forward" to 1,
|
||||
"round" to 1,
|
||||
"type" to "normal",
|
||||
"width" to 300
|
||||
),
|
||||
"desc" to "[聊天记录]",
|
||||
"extra" to mapOf(
|
||||
"filename" to uniseq,
|
||||
"tsum" to 2
|
||||
).json.toString(),
|
||||
"meta" to mapOf(
|
||||
"detail" to mapOf(
|
||||
"news" to desc.slice(0..if (i < 3) i else 3)
|
||||
.map { mapOf("text" to it) },
|
||||
"resid" to resid,
|
||||
"source" to "群聊的聊天记录",
|
||||
"summary" to "查看${msgs.size}条转发消息",
|
||||
"uniseq" to uniseq
|
||||
)
|
||||
),
|
||||
"prompt" to "[聊天记录]",
|
||||
"ver" to "0.0.0.5",
|
||||
"view" to "contact"
|
||||
),
|
||||
"resid" to resid
|
||||
)
|
||||
)
|
||||
).json, fromId, 3
|
||||
).getOrElse { return logic(it.message ?: "", echo) }
|
||||
val result = MsgSvc.sendToAio(chatType, peerId, listOf(message).toJson(), fromId, retryCnt)
|
||||
.getOrElse { return logic(it.message ?: "", echo) }
|
||||
|
||||
return ok(
|
||||
ForwardMessageResult(
|
||||
SendForwardMessageResult(
|
||||
msgId = result.msgHashId,
|
||||
forwardId = resid
|
||||
resId = message.data["id"] as String
|
||||
), echo = echo
|
||||
)
|
||||
}.onFailure {
|
||||
|
@ -9,9 +9,16 @@ import moe.fuqiuluo.symbols.OneBotHandler
|
||||
internal object SendGroupForwardMessage : IActionHandler() {
|
||||
override suspend fun internalHandle(session: ActionSession): String {
|
||||
val groupId = session.getLong("group_id")
|
||||
val retryCnt = session.getIntOrNull("retry_cnt") ?: 5
|
||||
return if (session.isArray("messages")) {
|
||||
val messages = session.getArray("messages")
|
||||
SendForwardMessage(MsgConstant.KCHATTYPEGROUP, groupId.toString(), messages, echo = session.echo)
|
||||
SendForwardMessage(
|
||||
MsgConstant.KCHATTYPEGROUP,
|
||||
groupId.toString(),
|
||||
messages = messages,
|
||||
retryCnt = retryCnt,
|
||||
echo = session.echo
|
||||
)
|
||||
} else {
|
||||
logic("未知格式合并转发消息", session.echo)
|
||||
}
|
||||
|
@ -41,27 +41,60 @@ internal object SendMessage: IActionHandler() {
|
||||
}
|
||||
}
|
||||
val peerId = when (chatType) {
|
||||
MsgConstant.KCHATTYPEGROUP -> session.getLongOrNull("group_id") ?: return noParam("group_id", session.echo)
|
||||
MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getLongOrNull("user_id") ?: return noParam("user_id", session.echo)
|
||||
MsgConstant.KCHATTYPEGROUP -> session.getLongOrNull("group_id") ?: return noParam(
|
||||
"group_id",
|
||||
session.echo
|
||||
)
|
||||
|
||||
MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getLongOrNull("user_id")
|
||||
?: return noParam("user_id", session.echo)
|
||||
|
||||
else -> error("unknown chat type: $chatType")
|
||||
}.toString()
|
||||
val fromId = when (chatType) {
|
||||
MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getLongOrNull("group_id") ?: return noParam("group_id", session.echo)
|
||||
MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getLongOrNull("group_id")
|
||||
?: return noParam("group_id", session.echo)
|
||||
|
||||
MsgConstant.KCHATTYPEC2C -> session.getLongOrNull("user_id") ?: return noParam("user_id", session.echo)
|
||||
else -> error("unknown chat type: $chatType")
|
||||
}.toString()
|
||||
val retryCnt = session.getIntOrNull("retry_cnt")
|
||||
val retryCnt = session.getIntOrNull("retry_cnt") ?: 5
|
||||
val recallDuration = session.getLongOrNull("recall_duration")
|
||||
return if (session.isString("message")) {
|
||||
val autoEscape = session.getBooleanOrDefault("auto_escape", false)
|
||||
val message = session.getString("message")
|
||||
invoke(chatType, peerId, message, autoEscape, echo = session.echo, fromId = fromId, retryCnt = retryCnt ?: 5, recallDuration = recallDuration)
|
||||
invoke(
|
||||
chatType,
|
||||
peerId,
|
||||
message,
|
||||
autoEscape,
|
||||
echo = session.echo,
|
||||
fromId = fromId,
|
||||
retryCnt = retryCnt,
|
||||
recallDuration = recallDuration
|
||||
)
|
||||
} else if (session.isArray("message")) {
|
||||
val message = session.getArray("message")
|
||||
invoke(chatType, peerId, message, session.echo, fromId = fromId, retryCnt ?: 5, recallDuration = recallDuration)
|
||||
invoke(
|
||||
chatType,
|
||||
peerId,
|
||||
message,
|
||||
session.echo,
|
||||
fromId = fromId,
|
||||
retryCnt = retryCnt,
|
||||
recallDuration = recallDuration
|
||||
)
|
||||
} else {
|
||||
val message = session.getObject("message")
|
||||
invoke(chatType, peerId, listOf( message ).jsonArray, session.echo, fromId = fromId, retryCnt ?: 5, recallDuration = recallDuration)
|
||||
invoke(
|
||||
chatType,
|
||||
peerId,
|
||||
listOf(message).jsonArray,
|
||||
session.echo,
|
||||
fromId = fromId,
|
||||
retryCnt = retryCnt,
|
||||
recallDuration = recallDuration
|
||||
)
|
||||
}
|
||||
} catch (e: ParamsException) {
|
||||
return noParam(e.message!!, session.echo)
|
||||
@ -82,14 +115,16 @@ internal object SendMessage: IActionHandler() {
|
||||
echo: JsonElement = EmptyJsonString
|
||||
): String {
|
||||
val result = if (autoEscape) {
|
||||
MsgSvc.sendToAio(chatType, peerId, listOf(
|
||||
MsgSvc.sendToAio(
|
||||
chatType, peerId, listOf(
|
||||
mapOf(
|
||||
"type" to "text",
|
||||
"data" to mapOf(
|
||||
"text" to message
|
||||
)
|
||||
)
|
||||
).json, fromId = fromId, retryCnt)
|
||||
).json, fromId = fromId, retryCnt
|
||||
)
|
||||
} else {
|
||||
val msg = MessageHelper.decodeCQCode(message)
|
||||
if (msg.isEmpty()) {
|
||||
@ -98,44 +133,54 @@ internal object SendMessage: IActionHandler() {
|
||||
} else {
|
||||
MsgSvc.sendToAio(chatType, peerId, msg, fromId = fromId, retryCnt)
|
||||
}
|
||||
}
|
||||
if (result.isFailure) {
|
||||
return logic(result.exceptionOrNull()?.message ?: "", echo)
|
||||
}
|
||||
val sendMsgResult = result.getOrThrow()
|
||||
if (sendMsgResult.msgHashId <= 0) {
|
||||
}.getOrElse{ return logic(it.message ?: "", echo)}
|
||||
if (result.msgHashId <= 0) {
|
||||
return logic("send message failed", echo = echo)
|
||||
}
|
||||
recallDuration?.let { autoRecall(sendMsgResult.msgHashId, it) }
|
||||
return ok(MessageResult(
|
||||
msgId = sendMsgResult.msgHashId,
|
||||
time = (sendMsgResult.msgTime * 0.001).toLong()
|
||||
), echo = echo)
|
||||
if (recallDuration != null) {
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
delay(recallDuration)
|
||||
MsgSvc.recallMsg(result.msgHashId)
|
||||
}
|
||||
}
|
||||
return ok(
|
||||
MessageResult(
|
||||
msgId = result.msgHashId,
|
||||
time = (result.msgTime * 0.001).toLong()
|
||||
), echo = echo
|
||||
)
|
||||
}
|
||||
|
||||
// 消息段格式消息
|
||||
suspend operator fun invoke(
|
||||
chatType: Int, peerId: String, message: JsonArray, echo: JsonElement = EmptyJsonString, fromId: String = peerId, retryCnt: Int, recallDuration: Long?,
|
||||
chatType: Int,
|
||||
peerId: String,
|
||||
message: JsonArray,
|
||||
echo: JsonElement = EmptyJsonString,
|
||||
fromId: String = peerId,
|
||||
retryCnt: Int,
|
||||
recallDuration: Long?,
|
||||
): String {
|
||||
//if (!ContactHelper.checkContactAvailable(chatType, peerId)) {
|
||||
// return logic("contact is not found", echo = echo)
|
||||
//}
|
||||
val result = MsgSvc.sendToAio(chatType, peerId, message, fromId = fromId, retryCnt).getOrElse { return logic(it.message ?: "", echo) }
|
||||
val result = MsgSvc.sendToAio(chatType, peerId, message, fromId, retryCnt)
|
||||
.getOrElse { return logic(it.message ?: "", echo) }
|
||||
if (result.msgHashId <= 0) {
|
||||
return logic("send message failed", echo = echo)
|
||||
}
|
||||
recallDuration?.let { autoRecall(result.msgHashId, it) }
|
||||
return ok(MessageResult(
|
||||
if (recallDuration != null) {
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
delay(recallDuration)
|
||||
MsgSvc.recallMsg(result.msgHashId)
|
||||
}
|
||||
}
|
||||
return ok(
|
||||
MessageResult(
|
||||
msgId = result.msgHashId,
|
||||
time = (result.msgTime * 0.001).toLong()
|
||||
), echo)
|
||||
}
|
||||
|
||||
private fun autoRecall(msgHash: Int, duration: Long) {
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
delay(duration)
|
||||
MsgSvc.recallMsg(msgHash)
|
||||
}
|
||||
), echo
|
||||
)
|
||||
}
|
||||
|
||||
override val requiredParams: Array<String> = arrayOf("message")
|
||||
|
@ -9,9 +9,18 @@ import moe.fuqiuluo.symbols.OneBotHandler
|
||||
internal object SendPrivateForwardMessage : IActionHandler() {
|
||||
override suspend fun internalHandle(session: ActionSession): String {
|
||||
val userId = session.getLong("user_id")
|
||||
val groupId = session.getLongOrNull("group_id")
|
||||
val retryCnt = session.getIntOrNull("retry_cnt") ?: 5
|
||||
return if (session.isArray("messages")) {
|
||||
val messages = session.getArray("messages")
|
||||
SendForwardMessage(MsgConstant.KCHATTYPEC2C, userId.toString(), messages, echo = session.echo)
|
||||
SendForwardMessage(
|
||||
MsgConstant.KCHATTYPEC2C,
|
||||
userId.toString(),
|
||||
groupId?.toString() ?: userId.toString(),
|
||||
messages,
|
||||
retryCnt,
|
||||
echo = session.echo
|
||||
)
|
||||
} else {
|
||||
logic("未知格式合并转发消息", session.echo)
|
||||
}
|
||||
|
@ -0,0 +1,97 @@
|
||||
package moe.fuqiuluo.shamrock.remote.action.handlers
|
||||
|
||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||
import kotlinx.serialization.json.*
|
||||
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
||||
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
||||
import moe.fuqiuluo.shamrock.helper.ParamsException
|
||||
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
||||
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
|
||||
import moe.fuqiuluo.shamrock.remote.service.data.UploadForwardMessageResult
|
||||
import moe.fuqiuluo.shamrock.tools.*
|
||||
import moe.fuqiuluo.symbols.OneBotHandler
|
||||
|
||||
@OneBotHandler("upload_multi_message")
|
||||
internal object UploadMultiMessage : IActionHandler() {
|
||||
override suspend fun internalHandle(session: ActionSession): String {
|
||||
val detailType = session.getStringOrNull("detail_type") ?: session.getStringOrNull("message_type")
|
||||
try {
|
||||
val chatType = detailType?.let {
|
||||
MessageHelper.obtainMessageTypeByDetailType(it)
|
||||
} ?: run {
|
||||
if (session.has("user_id")) {
|
||||
if (session.has("group_id")) {
|
||||
MsgConstant.KCHATTYPETEMPC2CFROMGROUP
|
||||
} else {
|
||||
MsgConstant.KCHATTYPEC2C
|
||||
}
|
||||
} else if (session.has("group_id")) {
|
||||
MsgConstant.KCHATTYPEGROUP
|
||||
} else {
|
||||
return noParam("detail_type/message_type", session.echo)
|
||||
}
|
||||
}
|
||||
val peerId = when (chatType) {
|
||||
MsgConstant.KCHATTYPEGROUP -> session.getLongOrNull("group_id") ?: return noParam(
|
||||
"group_id",
|
||||
session.echo
|
||||
)
|
||||
|
||||
MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getLongOrNull("user_id")
|
||||
?: return noParam("user_id", session.echo)
|
||||
|
||||
else -> error("unknown chat type: $chatType")
|
||||
}.toString()
|
||||
val fromId = when (chatType) {
|
||||
MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getLongOrNull("group_id")
|
||||
?: return noParam("group_id", session.echo)
|
||||
|
||||
MsgConstant.KCHATTYPEC2C -> session.getLongOrNull("user_id") ?: return noParam(
|
||||
"user_id",
|
||||
session.echo
|
||||
)
|
||||
|
||||
else -> error("unknown chat type: $chatType")
|
||||
}.toString()
|
||||
val retryCnt = session.getIntOrNull("retry_cnt") ?: 5
|
||||
return if (session.isArray("messages")) {
|
||||
val messages = session.getArray("messages")
|
||||
invoke(chatType, peerId, fromId, messages, retryCnt, echo = session.echo)
|
||||
} else {
|
||||
logic("未知格式合并转发消息", session.echo)
|
||||
}
|
||||
} catch (e: ParamsException) {
|
||||
return noParam(e.message!!, session.echo)
|
||||
} catch (e: Throwable) {
|
||||
return logic(e.message ?: e.toString(), session.echo)
|
||||
}
|
||||
}
|
||||
|
||||
suspend operator fun invoke(
|
||||
chatType: Int,
|
||||
peerId: String,
|
||||
fromId: String = peerId,
|
||||
messages: JsonArray,
|
||||
retryCnt: Int,
|
||||
echo: JsonElement = EmptyJsonString
|
||||
): String {
|
||||
kotlin.runCatching {
|
||||
val message = MsgSvc.uploadMultiMsg(chatType, peerId, fromId, messages, retryCnt)
|
||||
.getOrElse { return logic(it.message ?: "", echo) }
|
||||
|
||||
return ok(
|
||||
UploadForwardMessageResult(
|
||||
resId = message.data["id"] as String,
|
||||
filename = message.data["filename"] as String,
|
||||
summary = message.data["summary"] as String,
|
||||
desc = message.data["desc"] as String
|
||||
), echo = echo
|
||||
)
|
||||
}.onFailure {
|
||||
return error("合并转发消息失败: $it", echo)
|
||||
}
|
||||
return logic("合并转发消息失败(unknown error)", echo)
|
||||
}
|
||||
|
||||
override val requiredParams: Array<String> = arrayOf("messages")
|
||||
}
|
@ -44,49 +44,6 @@ fun Routing.messageAction() {
|
||||
}
|
||||
}
|
||||
|
||||
route("/send_group_forward_(msg|message)".toRegex()) {
|
||||
post {
|
||||
val groupId = fetchPostOrNull("group_id")
|
||||
val messages = fetchPostJsonArray("messages")
|
||||
call.respondText(
|
||||
SendForwardMessage(MsgConstant.KCHATTYPEGROUP, groupId ?: "", messages),
|
||||
ContentType.Application.Json
|
||||
)
|
||||
}
|
||||
get {
|
||||
respond(false, Status.InternalHandlerError, "Not support GET method")
|
||||
}
|
||||
}
|
||||
|
||||
route("/send_private_forward_(msg|message)".toRegex()) {
|
||||
post {
|
||||
val userId = fetchPostOrNull("user_id")
|
||||
val messages = fetchPostJsonArray("messages")
|
||||
call.respondText(
|
||||
SendForwardMessage(MsgConstant.KCHATTYPEC2C, userId ?: "", messages),
|
||||
ContentType.Application.Json
|
||||
)
|
||||
}
|
||||
get {
|
||||
respond(false, Status.InternalHandlerError, "Not support GET method")
|
||||
}
|
||||
}
|
||||
|
||||
route("/send_forward_(msg|message)".toRegex()) {
|
||||
post {
|
||||
val userId = fetchPostOrNull("user_id")
|
||||
val groupId = fetchPostOrNull("group_id")
|
||||
val messages = fetchPostJsonArray("messages")
|
||||
call.respondText(
|
||||
SendForwardMessage(MsgConstant.KCHATTYPEC2C, userId ?: groupId ?: "", messages),
|
||||
ContentType.Application.Json
|
||||
)
|
||||
}
|
||||
get {
|
||||
respond(false, Status.InternalHandlerError, "Not support GET method")
|
||||
}
|
||||
}
|
||||
|
||||
getOrPost("/get_forward_msg") {
|
||||
val id = fetchOrThrow("id")
|
||||
call.respondText(GetForwardMsg(id), ContentType.Application.Json)
|
||||
@ -144,7 +101,7 @@ fun Routing.messageAction() {
|
||||
get {
|
||||
val msgType = fetchGetOrThrow("message_type")
|
||||
val message = fetchGetOrThrow("message")
|
||||
val retryCnt = fetchGetOrNull("retry_cnt")?.toInt() ?: 3
|
||||
val retryCnt = fetchGetOrNull("retry_cnt")?.toInt() ?: 5
|
||||
val autoEscape = fetchGetOrNull("auto_escape")?.toBooleanStrict() ?: false
|
||||
val chatType = MessageHelper.obtainMessageTypeByDetailType(msgType)
|
||||
|
||||
@ -288,11 +245,11 @@ fun Routing.messageAction() {
|
||||
post {
|
||||
val userId = fetchPostOrThrow("user_id")
|
||||
val groupId = fetchPostOrNull("group_id")
|
||||
val retryCnt = fetchPostOrNull("retry_cnt")?.toInt() ?: 3
|
||||
val autoEscape = fetchPostOrNull("auto_escape")?.toBooleanStrict() ?: false
|
||||
|
||||
val chatType = if (groupId == null) MsgConstant.KCHATTYPEC2C else MsgConstant.KCHATTYPETEMPC2CFROMGROUP
|
||||
val fromId = groupId ?: userId
|
||||
|
||||
val retryCnt = fetchPostOrNull("retry_cnt")?.toInt() ?: 3
|
||||
val autoEscape = fetchPostOrNull("auto_escape")?.toBooleanStrict() ?: false
|
||||
val recallDuration = fetchPostOrNull("recall_duration")?.toLongOrNull()
|
||||
|
||||
val result = if (isJsonData()) {
|
||||
@ -302,8 +259,9 @@ fun Routing.messageAction() {
|
||||
peerId = userId,
|
||||
message = fetchPostJsonString("message"),
|
||||
autoEscape = autoEscape,
|
||||
fromId = fromId,
|
||||
retryCnt = retryCnt, recallDuration = recallDuration
|
||||
fromId = groupId ?: userId,
|
||||
retryCnt = retryCnt,
|
||||
recallDuration = recallDuration
|
||||
)
|
||||
} else {
|
||||
SendMessage(
|
||||
@ -312,7 +270,7 @@ fun Routing.messageAction() {
|
||||
message = if (isJsonObject("message")) listOf(fetchPostJsonObject("message")).jsonArray else fetchPostJsonArray(
|
||||
"message"
|
||||
),
|
||||
fromId = groupId ?: userId ?: "",
|
||||
fromId = groupId ?: userId,
|
||||
retryCnt = retryCnt,
|
||||
recallDuration = recallDuration
|
||||
)
|
||||
@ -323,12 +281,96 @@ fun Routing.messageAction() {
|
||||
peerId = userId,
|
||||
message = fetchPostOrThrow("message"),
|
||||
autoEscape = autoEscape,
|
||||
fromId = fromId,
|
||||
retryCnt = retryCnt, recallDuration = recallDuration
|
||||
fromId = groupId ?: userId,
|
||||
retryCnt = retryCnt,
|
||||
recallDuration = recallDuration
|
||||
)
|
||||
}
|
||||
|
||||
call.respondText(result, ContentType.Application.Json)
|
||||
}
|
||||
}
|
||||
|
||||
route("/upload_multi_(msg|message)".toRegex()) {
|
||||
post {
|
||||
val msgType = fetchPostOrThrow("message_type")
|
||||
val chatType = MessageHelper.obtainMessageTypeByDetailType(msgType)
|
||||
val retryCnt = fetchPostOrNull("retry_cnt")?.toInt() ?: 5
|
||||
|
||||
val userId = fetchPostOrNull("user_id")
|
||||
val groupId = fetchPostOrNull("group_id")
|
||||
val messages = fetchPostJsonArray("messages")
|
||||
call.respondText(
|
||||
UploadMultiMessage(
|
||||
chatType,
|
||||
if (chatType == MsgConstant.KCHATTYPEC2C) userId!! else groupId!!,
|
||||
groupId ?: userId ?: "",
|
||||
messages,
|
||||
retryCnt
|
||||
),
|
||||
ContentType.Application.Json
|
||||
)
|
||||
}
|
||||
get {
|
||||
respond(false, Status.InternalHandlerError, "Not support GET method")
|
||||
}
|
||||
}
|
||||
|
||||
route("/send_forward_(msg|message)".toRegex()) {
|
||||
post {
|
||||
val msgType = fetchPostOrThrow("message_type")
|
||||
val chatType = MessageHelper.obtainMessageTypeByDetailType(msgType)
|
||||
val retryCnt = fetchPostOrNull("retry_cnt")?.toInt() ?: 5
|
||||
|
||||
val userId = fetchPostOrNull("user_id")
|
||||
val groupId = fetchPostOrNull("group_id")
|
||||
val messages = fetchPostJsonArray("messages")
|
||||
call.respondText(
|
||||
SendForwardMessage(
|
||||
chatType,
|
||||
if (chatType == MsgConstant.KCHATTYPEC2C) userId!! else groupId!!,
|
||||
groupId ?: userId ?: "",
|
||||
messages,
|
||||
retryCnt
|
||||
),
|
||||
ContentType.Application.Json
|
||||
)
|
||||
}
|
||||
get {
|
||||
respond(false, Status.InternalHandlerError, "Not support GET method")
|
||||
}
|
||||
}
|
||||
|
||||
route("/send_private_forward_(msg|message)".toRegex()) {
|
||||
post {
|
||||
val userId = fetchPostOrThrow("user_id")
|
||||
val groupId = fetchPostOrNull("group_id")
|
||||
|
||||
val retryCnt = fetchPostOrNull("retry_cnt")?.toInt() ?: 5
|
||||
val messages = fetchPostJsonArray("messages")
|
||||
call.respondText(
|
||||
SendForwardMessage(MsgConstant.KCHATTYPEC2C, userId, groupId ?: userId, messages, retryCnt),
|
||||
ContentType.Application.Json
|
||||
)
|
||||
}
|
||||
get {
|
||||
respond(false, Status.InternalHandlerError, "Not support GET method")
|
||||
}
|
||||
}
|
||||
|
||||
route("/send_group_forward_(msg|message)".toRegex()) {
|
||||
post {
|
||||
val groupId = fetchPostOrThrow("group_id")
|
||||
|
||||
val retryCnt = fetchPostOrNull("retry_cnt")?.toInt() ?: 5
|
||||
val messages = fetchPostJsonArray("messages")
|
||||
call.respondText(
|
||||
SendForwardMessage(MsgConstant.KCHATTYPEGROUP, groupId, messages = messages, retryCnt = retryCnt),
|
||||
ContentType.Application.Json
|
||||
)
|
||||
}
|
||||
get {
|
||||
respond(false, Status.InternalHandlerError, "Not support GET method")
|
||||
}
|
||||
}
|
||||
}
|
@ -9,10 +9,20 @@ internal data class MessageResult(
|
||||
@SerialName("message_id") val msgId: Int,
|
||||
@SerialName("time") val time: Long
|
||||
)
|
||||
|
||||
@Serializable
|
||||
internal data class ForwardMessageResult(
|
||||
internal data class UploadForwardMessageResult(
|
||||
@SerialName("res_id") val resId: String,
|
||||
@SerialName("filename") val filename: String,
|
||||
@SerialName("summary") val summary: String,
|
||||
@SerialName("desc") val desc: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
internal data class SendForwardMessageResult(
|
||||
@SerialName("message_id") val msgId: Int,
|
||||
@SerialName("forward_id") val forwardId: String
|
||||
@SerialName("res_id") val resId: String,
|
||||
@SerialName("forward_id") val forwardId: String = resId
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@ -20,6 +30,7 @@ internal data class MessageDetail(
|
||||
@SerialName("time") val time: Int,
|
||||
@SerialName("message_type") val msgType: String,
|
||||
@SerialName("message_id") val msgId: Int,
|
||||
@SerialName("message_id_qq") val qqMsgId: Long,
|
||||
@SerialName("message_seq") val msgSeq: Long,
|
||||
@SerialName("real_id") val realId: Long,
|
||||
@SerialName("sender") val sender: MessageSender,
|
||||
@ -29,6 +40,11 @@ internal data class MessageDetail(
|
||||
@SerialName("target_id") val targetId: Long = 0,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
internal data class GetForwardMsgResult(
|
||||
@SerialName("messages") val msgs: List<MessageDetail>
|
||||
)
|
||||
|
||||
@Serializable
|
||||
internal data class MessageSender(
|
||||
@SerialName("user_id") val userId: Long,
|
||||
|
@ -69,7 +69,6 @@ internal object PrimitiveListener {
|
||||
528 -> when (subType) {
|
||||
35 -> onFriendApply(msgTime, push.clientInfo!!, body)
|
||||
39 -> onCardChange(msgTime, body)
|
||||
// invite
|
||||
68 -> onGroupApply(msgTime, contentHead, body)
|
||||
138 -> onC2CRecall(msgTime, body)
|
||||
290 -> onC2CPoke(msgTime, body)
|
||||
@ -79,7 +78,7 @@ internal object PrimitiveListener {
|
||||
12 -> onGroupBan(msgTime, body)
|
||||
16 -> onGroupUniqueTitleChange(msgTime, body)
|
||||
17 -> onGroupRecall(msgTime, body)
|
||||
20 -> onGroupPokeAndGroupSign(msgTime, body)
|
||||
20 -> onGroupCommonTips(msgTime, body)
|
||||
21 -> onEssenceMessage(msgTime, push.clientInfo, body)
|
||||
}
|
||||
}
|
||||
@ -281,7 +280,7 @@ internal object PrimitiveListener {
|
||||
}
|
||||
|
||||
|
||||
private suspend fun onGroupPokeAndGroupSign(time: Long, body: MsgBody) {
|
||||
private suspend fun onGroupCommonTips(time: Long, body: MsgBody) {
|
||||
val event = runCatching {
|
||||
body.msgContent!!.decodeProtobuf<GroupCommonTipsEvent>()
|
||||
}.getOrElse {
|
||||
|
@ -52,6 +52,7 @@ val Collection<Any?>.json: JsonArray
|
||||
is Number -> arrayList.add(it.json)
|
||||
is String -> arrayList.add(it.json)
|
||||
is Boolean -> arrayList.add(it.json)
|
||||
is Pair<*, *> -> arrayList.add(mapOf(it as Pair<String, Any?>).json)
|
||||
is Map<*, *> -> arrayList.add((it as Map<String, Any?>).json)
|
||||
is Collection<*> -> arrayList.add((it as Collection<Any?>).json)
|
||||
else -> error("unknown array type: ${it::class.java}")
|
||||
|
Loading…
x
Reference in New Issue
Block a user