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
c70f3eabfe
commit
aa7b241dba
@ -6,4 +6,33 @@ import kotlinx.serialization.protobuf.ProtoNumber
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class TextElement(
|
data class TextElement(
|
||||||
@ProtoNumber(1) val text: String? = null,
|
@ProtoNumber(1) val text: String? = null,
|
||||||
|
@ProtoNumber(2) val link: String? = null,
|
||||||
|
@ProtoNumber(3) val attr6Buf: ByteArray? = null,
|
||||||
|
@ProtoNumber(4) val attr7Buf: ByteArray? = null,
|
||||||
|
@ProtoNumber(11) val buf: ByteArray? = null,
|
||||||
|
@ProtoNumber(12) val pbReserve: TextResvAttr? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class TextResvAttr(
|
||||||
|
@ProtoNumber(1) val wording: ByteArray? = null,
|
||||||
|
@ProtoNumber(2) val textAnalysisResult: Int? = null,
|
||||||
|
@ProtoNumber(3) val atType: Int? = null,
|
||||||
|
@ProtoNumber(4) val atMemberUin: Long? = null,
|
||||||
|
@ProtoNumber(5) val atMemberTinyid: Long? = null,
|
||||||
|
@ProtoNumber(6) val atChannelInfo: ExtChannelInfo? = null,
|
||||||
|
@ProtoNumber(7) val atRoleInfo: ExtRoleInfo? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ExtChannelInfo(
|
||||||
|
@ProtoNumber(1) val guildId: Long? = null,
|
||||||
|
@ProtoNumber(2) val channelId: Long? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ExtRoleInfo(
|
||||||
|
@ProtoNumber(1) val id: Long? = null,
|
||||||
|
@ProtoNumber(2) val info: ByteArray? = null,
|
||||||
|
@ProtoNumber(3) val flag: Int? = null,
|
||||||
)
|
)
|
@ -23,7 +23,7 @@ data class LongMsgUid(
|
|||||||
data class RecvLongMsgInfo(
|
data class RecvLongMsgInfo(
|
||||||
@ProtoNumber(1) val uid: LongMsgUid? = null,
|
@ProtoNumber(1) val uid: LongMsgUid? = null,
|
||||||
@ProtoNumber(2) val resId: String? = null,
|
@ProtoNumber(2) val resId: String? = null,
|
||||||
@ProtoNumber(3) val acquire: Boolean? = null,
|
@ProtoNumber(3) val u1: Int? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -28,5 +28,5 @@ data class LongMsgAction(
|
|||||||
)
|
)
|
||||||
@Serializable
|
@Serializable
|
||||||
data class LongMsgPayload(
|
data class LongMsgPayload(
|
||||||
@ProtoNumber(2) val action: LongMsgAction? = null
|
@ProtoNumber(2) val action: List<LongMsgAction>? = null
|
||||||
)
|
)
|
@ -8,36 +8,29 @@ import com.tencent.qqnt.kernel.api.IKernelService
|
|||||||
import com.tencent.qqnt.kernel.nativeinterface.*
|
import com.tencent.qqnt.kernel.nativeinterface.*
|
||||||
import com.tencent.qqnt.msg.api.IMsgService
|
import com.tencent.qqnt.msg.api.IMsgService
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
import kotlinx.serialization.decodeFromByteArray
|
import kotlinx.serialization.decodeFromByteArray
|
||||||
import kotlinx.serialization.encodeToByteArray
|
import kotlinx.serialization.encodeToByteArray
|
||||||
import kotlinx.serialization.json.JsonArray
|
import kotlinx.serialization.json.JsonArray
|
||||||
import kotlinx.serialization.json.JsonObject
|
|
||||||
import kotlinx.serialization.protobuf.ProtoBuf
|
import kotlinx.serialization.protobuf.ProtoBuf
|
||||||
import moe.fuqiuluo.qqinterface.servlet.msg.convert.toSegments
|
import moe.fuqiuluo.qqinterface.servlet.msg.messageelement.toSegments
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.msg.toListMap
|
||||||
import moe.fuqiuluo.shamrock.helper.ContactHelper
|
import moe.fuqiuluo.shamrock.helper.ContactHelper
|
||||||
import moe.fuqiuluo.shamrock.helper.Level
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
||||||
import moe.fuqiuluo.shamrock.helper.MessageHelper.messageArrayToMessageElements
|
import moe.fuqiuluo.shamrock.remote.service.data.MessageDetail
|
||||||
|
import moe.fuqiuluo.shamrock.remote.service.data.MessageSender
|
||||||
import moe.fuqiuluo.shamrock.remote.structures.SendMsgResult
|
import moe.fuqiuluo.shamrock.remote.structures.SendMsgResult
|
||||||
import moe.fuqiuluo.shamrock.tools.*
|
import moe.fuqiuluo.shamrock.tools.*
|
||||||
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.msgService
|
import moe.fuqiuluo.shamrock.xposed.helper.msgService
|
||||||
import protobuf.message.*
|
|
||||||
import protobuf.message.longmsg.*
|
import protobuf.message.longmsg.*
|
||||||
import tencent.mobileim.structmsg.structmsg.SystemMsg
|
|
||||||
import java.util.UUID
|
|
||||||
import kotlin.collections.slice
|
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
import kotlin.random.Random
|
|
||||||
import kotlin.random.nextLong
|
|
||||||
|
|
||||||
internal object MsgSvc : BaseSvc() {
|
internal object MsgSvc : BaseSvc() {
|
||||||
suspend fun prepareTempChatFromGroup(
|
suspend fun prepareTempChatFromGroup(
|
||||||
@ -237,10 +230,12 @@ internal object MsgSvc : BaseSvc() {
|
|||||||
messages: List<PushMsgBody>,
|
messages: List<PushMsgBody>,
|
||||||
): Result<String> {
|
): Result<String> {
|
||||||
val payload = LongMsgPayload(
|
val payload = LongMsgPayload(
|
||||||
action = LongMsgAction(
|
action = listOf(
|
||||||
command = "MultiMsg",
|
LongMsgAction(
|
||||||
data = LongMsgContent(
|
command = "MultiMsg",
|
||||||
messages
|
data = LongMsgContent(
|
||||||
|
body = messages
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -265,49 +260,64 @@ internal object MsgSvc : BaseSvc() {
|
|||||||
ProtoBuf.encodeToByteArray(req)
|
ProtoBuf.encodeToByteArray(req)
|
||||||
) ?: return Result.failure(Exception("unable to upload multi message"))
|
) ?: return Result.failure(Exception("unable to upload multi message"))
|
||||||
val rsp = ProtoBuf.decodeFromByteArray<LongMsgRsp>(buffer.slice(4))
|
val rsp = ProtoBuf.decodeFromByteArray<LongMsgRsp>(buffer.slice(4))
|
||||||
return rsp.sendResult?.resId?.let { Result.success(it) } ?: Result.failure(Exception("unable to upload multi message"))
|
return rsp.sendResult?.resId?.let { Result.success(it) }
|
||||||
|
?: Result.failure(Exception("unable to upload multi message"))
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getMultiMsg(resId: String): Result<List<MsgRecord>> {
|
suspend fun getMultiMsg(resId: String): Result<List<MessageDetail>> {
|
||||||
// trpc.group.long_msg_interface.MsgService.SsoRecvLongMsg
|
val req = LongMsgReq(
|
||||||
// 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
|
recvInfo = RecvLongMsgInfo(
|
||||||
|
uid = LongMsgUid(TicketSvc.getUid()),
|
||||||
val kernelService = NTServiceFetcher.kernelService
|
resId = resId,
|
||||||
val sessionService = kernelService.wrapperSession
|
u1 = 3
|
||||||
val msgService = sessionService.msgService
|
),
|
||||||
val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEC2C, TicketSvc.getUin())
|
setting = LongMsgSettings(
|
||||||
|
field1 = 2,
|
||||||
val content =
|
field2 = 2,
|
||||||
"{\"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\"}"
|
field3 = 9,
|
||||||
val msgId = PacketSvc.fakeSelfRecvJsonMsg(msgService, content)
|
field4 = 0
|
||||||
if (msgId < 0) {
|
)
|
||||||
return Result.failure(Exception("获取合并转发消息ID失败"))
|
)
|
||||||
}
|
val buffer = sendBufferAW(
|
||||||
val msgList = withTimeoutOrNull(5000L) {
|
"trpc.group.long_msg_interface.MsgService.SsoRecvLongMsg",
|
||||||
suspendCancellableCoroutine<ArrayList<MsgRecord>> {
|
true,
|
||||||
val job = GlobalScope.launch {
|
ProtoBuf.encodeToByteArray(req)
|
||||||
var hasResult = false
|
) ?: return Result.failure(Exception("unable to get multi message"))
|
||||||
while (!hasResult) {
|
val rsp = ProtoBuf.decodeFromByteArray<LongMsgRsp>(buffer.slice(4))
|
||||||
msgService.getMultiMsg(contact, msgId, msgId) { code, why, msgList ->
|
val msg = DeflateTools.ungzip(
|
||||||
if (code == 0) {
|
rsp.recvResult?.payload ?: return Result.failure(Exception("unable to get multi message"))
|
||||||
it.resume(msgList)
|
)
|
||||||
hasResult = true
|
val payload = ProtoBuf.decodeFromByteArray<LongMsgPayload>(msg)
|
||||||
} else {
|
payload.action?.forEach {
|
||||||
LogCenter.log("获取合并转发消息失败: $code($why): $msgId", Level.ERROR)
|
if (it.command == "MultiMsg") {
|
||||||
}
|
return Result.success(it.data?.body?.map { msg ->
|
||||||
}
|
val chatType =
|
||||||
delay(200)
|
if (msg.content!!.msgType == 1) MsgConstant.KCHATTYPEC2C else MsgConstant.KCHATTYPEGROUP
|
||||||
}
|
MessageDetail(
|
||||||
}
|
time = msg.content?.msgTime?.toInt() ?: 0,
|
||||||
it.invokeOnCancellation {
|
msgType = MessageHelper.obtainDetailTypeByMsgType(chatType),
|
||||||
job.cancel()
|
msgId = MessageHelper.generateMsgIdHash(chatType, msg.content!!.msgViaRandom),
|
||||||
|
realId = msg.content!!.msgSeq.toInt(),
|
||||||
|
sender = MessageSender(
|
||||||
|
msg.head?.peer ?: 0,
|
||||||
|
msg.head!!.groupInfo!!.memberCard ?: "",
|
||||||
|
"unknown",
|
||||||
|
0,
|
||||||
|
msg.head!!.peerUid!!,
|
||||||
|
msg.head!!.peerUid!!
|
||||||
|
),
|
||||||
|
message = msg.body?.rich?.elements?.toSegments(chatType, msg.head?.peer.toString(), "0")
|
||||||
|
?.toListMap() ?: emptyList(),
|
||||||
|
peerId = msg.head?.peer ?: 0,
|
||||||
|
groupId = if (chatType == MsgConstant.KCHATTYPEGROUP) msg.head?.groupInfo?.groupCode?.toLong()
|
||||||
|
?: 0 else 0,
|
||||||
|
targetId = if (chatType != MsgConstant.KCHATTYPEGROUP) msg.head?.peer ?: 0 else 0
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
?: return Result.failure(Exception("Msg is empty")))
|
||||||
}
|
}
|
||||||
} ?: return Result.failure(Exception("获取合并转发消息失败"))
|
}
|
||||||
|
return Result.failure(Exception("Can't find msg"))
|
||||||
//msgService.deleteMsg(contact, arrayListOf(msgId), null)
|
|
||||||
|
|
||||||
return Result.success(msgList)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class MessageCallback(
|
class MessageCallback(
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
package moe.fuqiuluo.qqinterface.servlet.msg
|
||||||
|
|
||||||
|
import kotlinx.serialization.json.JsonArray
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import moe.fuqiuluo.shamrock.tools.json
|
||||||
|
|
||||||
|
|
||||||
|
internal data class MessageSegment(
|
||||||
|
val type: String,
|
||||||
|
val data: Map<String, Any> = emptyMap()
|
||||||
|
) {
|
||||||
|
fun toJson(): JsonObject {
|
||||||
|
return hashMapOf(
|
||||||
|
"type" to type.json,
|
||||||
|
"data" to data.json
|
||||||
|
).json
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun List<MessageSegment>.toJson(): JsonArray {
|
||||||
|
return this.map {
|
||||||
|
it.toJson()
|
||||||
|
}.json
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun List<MessageSegment>.toListMap(): List<Map<String, JsonElement>> {
|
||||||
|
return this.map {
|
||||||
|
hashMapOf(
|
||||||
|
"type" to it.type.json,
|
||||||
|
"data" to it.data.json
|
||||||
|
).json
|
||||||
|
}
|
||||||
|
}
|
@ -1,133 +0,0 @@
|
|||||||
package moe.fuqiuluo.qqinterface.servlet.msg.convert
|
|
||||||
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
|
|
||||||
import kotlinx.serialization.json.JsonElement
|
|
||||||
import moe.fuqiuluo.qqinterface.servlet.msg.convert.MessageElemConverter.*
|
|
||||||
import moe.fuqiuluo.shamrock.helper.Level
|
|
||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
|
||||||
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
|
||||||
import moe.fuqiuluo.shamrock.tools.json
|
|
||||||
|
|
||||||
internal typealias MessageSegmentList = ArrayList<MessageSegment>
|
|
||||||
|
|
||||||
internal data class MessageSegment(
|
|
||||||
val type: String,
|
|
||||||
val data: Map<String, Any> = emptyMap()
|
|
||||||
) {
|
|
||||||
fun toJson(): Map<String, JsonElement> {
|
|
||||||
return hashMapOf(
|
|
||||||
"type" to type.json,
|
|
||||||
"data" to data.json
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal suspend fun MsgRecord.toSegments(): ArrayList<MessageSegment> {
|
|
||||||
return MessageConvert.convertMessageRecordToMsgSegment(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal suspend fun MsgRecord.toCQCode(): String {
|
|
||||||
return MessageConvert.convertMessageRecordToCQCode(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal suspend fun List<MsgElement>.toSegments(chatType: Int, peerId: String, subPeer: String): MessageSegmentList {
|
|
||||||
return MessageConvert.convertMessageElementsToMsgSegment(chatType, this, peerId, subPeer)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal suspend fun List<MsgElement>.toCQCode(chatType: Int, peerId: String, subPeer: String): String {
|
|
||||||
return MessageConvert.convertMsgElementsToCQCode(this, chatType, peerId, subPeer)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal object MessageConvert {
|
|
||||||
private val convertMap by lazy {
|
|
||||||
mutableMapOf<Int, IMessageConvert>(
|
|
||||||
MsgConstant.KELEMTYPETEXT to TextConverter,
|
|
||||||
MsgConstant.KELEMTYPEFACE to FaceConverter,
|
|
||||||
MsgConstant.KELEMTYPEPIC to ImageConverter,
|
|
||||||
MsgConstant.KELEMTYPEPTT to VoiceConverter,
|
|
||||||
MsgConstant.KELEMTYPEVIDEO to VideoConverter,
|
|
||||||
MsgConstant.KELEMTYPEMARKETFACE to MarketFaceConverter,
|
|
||||||
MsgConstant.KELEMTYPEARKSTRUCT to StructJsonConverter,
|
|
||||||
MsgConstant.KELEMTYPEREPLY to ReplyConverter,
|
|
||||||
MsgConstant.KELEMTYPEGRAYTIP to GrayTipsConverter,
|
|
||||||
MsgConstant.KELEMTYPEFILE to FileConverter,
|
|
||||||
MsgConstant.KELEMTYPEMARKDOWN to MarkdownConverter,
|
|
||||||
//MsgConstant.KELEMTYPEMULTIFORWARD to XmlMultiMsgConverter,
|
|
||||||
//MsgConstant.KELEMTYPESTRUCTLONGMSG to XmlLongMsgConverter,
|
|
||||||
MsgConstant.KELEMTYPEFACEBUBBLE to BubbleFaceConverter,
|
|
||||||
MsgConstant.KELEMTYPEINLINEKEYBOARD to InlineKeyboardConverter,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun convertMessageElementsToMsgSegment(
|
|
||||||
chatType: Int,
|
|
||||||
elements: List<MsgElement>,
|
|
||||||
peerId: String,
|
|
||||||
subPeer: String
|
|
||||||
): ArrayList<MessageSegment> {
|
|
||||||
val messageData = arrayListOf<MessageSegment>()
|
|
||||||
elements.forEach { msg ->
|
|
||||||
kotlin.runCatching {
|
|
||||||
val elementId = msg.elementType
|
|
||||||
val converter = convertMap[elementId]
|
|
||||||
converter?.convert(chatType, peerId, subPeer, msg)
|
|
||||||
?: throw UnsupportedOperationException("不支持的消息element类型:$elementId")
|
|
||||||
}.onSuccess {
|
|
||||||
messageData.add(it)
|
|
||||||
}.onFailure {
|
|
||||||
if (it is UnknownError) {
|
|
||||||
// 不处理的消息类型,抛出unknown error
|
|
||||||
} else {
|
|
||||||
LogCenter.log("消息element转换错误:$it, elementType: ${msg.elementType}", Level.WARN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return messageData
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun convertMessageRecordToMsgSegment(record: MsgRecord, chatType: Int = record.chatType): ArrayList<MessageSegment> {
|
|
||||||
val peerId = when(chatType) {
|
|
||||||
MsgConstant.KCHATTYPEGUILD -> record.guildId
|
|
||||||
else -> record.peerUin.toString()
|
|
||||||
}
|
|
||||||
return convertMessageElementsToMsgSegment(chatType, record.elements, peerId, record.channelId ?: peerId)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun convertMsgElementsToCQCode(
|
|
||||||
elements: List<MsgElement>,
|
|
||||||
chatType: Int,
|
|
||||||
peerId: String,
|
|
||||||
subPeer: String
|
|
||||||
): String {
|
|
||||||
if(elements.isEmpty()) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
val msgList = convertMessageElementsToMsgSegment(chatType, elements, peerId, subPeer).map {
|
|
||||||
it.toJson()
|
|
||||||
}
|
|
||||||
return MessageHelper.encodeCQCode(msgList)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun convertMessageRecordToCQCode(record: MsgRecord, chatType: Int = record.chatType): String {
|
|
||||||
val peerId = when(chatType) {
|
|
||||||
MsgConstant.KCHATTYPEGUILD -> record.guildId
|
|
||||||
else -> record.peerUin.toString()
|
|
||||||
}
|
|
||||||
return MessageHelper.encodeCQCode(
|
|
||||||
convertMessageElementsToMsgSegment(
|
|
||||||
chatType,
|
|
||||||
record.elements,
|
|
||||||
peerId,
|
|
||||||
record.channelId ?: peerId
|
|
||||||
).map { it.toJson() }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun interface IMessageConvert {
|
|
||||||
suspend fun convert(chatType: Int, peerId: String, subPeer: String, element: MsgElement): MessageSegment
|
|
||||||
}
|
|
||||||
|
|
@ -1,558 +0,0 @@
|
|||||||
package moe.fuqiuluo.qqinterface.servlet.msg.convert
|
|
||||||
|
|
||||||
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.transfile.RichProtoSvc
|
|
||||||
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.db.ImageDB
|
|
||||||
import moe.fuqiuluo.shamrock.helper.db.ImageMapping
|
|
||||||
import moe.fuqiuluo.shamrock.helper.db.MessageDB
|
|
||||||
import moe.fuqiuluo.shamrock.tools.asJsonObject
|
|
||||||
import moe.fuqiuluo.shamrock.tools.asString
|
|
||||||
import moe.fuqiuluo.shamrock.tools.hex2ByteArray
|
|
||||||
import moe.fuqiuluo.shamrock.tools.json
|
|
||||||
|
|
||||||
internal sealed class MessageElemConverter: IMessageConvert {
|
|
||||||
/**
|
|
||||||
* 文本 / 艾特 消息转换消息段
|
|
||||||
*/
|
|
||||||
data object TextConverter: MessageElemConverter() {
|
|
||||||
override suspend fun convert(
|
|
||||||
chatType: Int,
|
|
||||||
peerId: String,
|
|
||||||
subPeer: String,
|
|
||||||
element: MsgElement
|
|
||||||
): MessageSegment {
|
|
||||||
val text = element.textElement
|
|
||||||
return if (text.atType != MsgConstant.ATTYPEUNKNOWN) {
|
|
||||||
MessageSegment(
|
|
||||||
type = "at",
|
|
||||||
data = hashMapOf(
|
|
||||||
"qq" to ContactHelper.getUinByUidAsync(text.atNtUid),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
MessageSegment(
|
|
||||||
type = "text",
|
|
||||||
data = hashMapOf(
|
|
||||||
"text" to text.content
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 小表情 / 戳一戳 消息转换消息段
|
|
||||||
*/
|
|
||||||
data object FaceConverter: MessageElemConverter() {
|
|
||||||
override suspend fun convert(
|
|
||||||
chatType: Int,
|
|
||||||
peerId: String,
|
|
||||||
subPeer: String,
|
|
||||||
element: MsgElement
|
|
||||||
): MessageSegment {
|
|
||||||
val face = element.faceElement
|
|
||||||
|
|
||||||
if (face.faceType == 5) {
|
|
||||||
return MessageSegment(
|
|
||||||
type = "poke",
|
|
||||||
data = hashMapOf(
|
|
||||||
"type" to face.pokeType,
|
|
||||||
"id" to face.vaspokeId,
|
|
||||||
"strength" to face.pokeStrength
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
when (face.faceIndex) {
|
|
||||||
114 -> {
|
|
||||||
return MessageSegment(
|
|
||||||
type = "basketball",
|
|
||||||
data = hashMapOf(
|
|
||||||
"id" to face.resultId.ifEmpty { "0" }.toInt(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
358 -> {
|
|
||||||
if (face.sourceType == 1) return MessageSegment("new_dice")
|
|
||||||
return MessageSegment(
|
|
||||||
type = "new_dice",
|
|
||||||
data = hashMapOf(
|
|
||||||
"id" to face.resultId.ifEmpty { "0" }.toInt()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
359 -> {
|
|
||||||
if (face.resultId.isEmpty()) return MessageSegment("new_rps")
|
|
||||||
return MessageSegment(
|
|
||||||
type = "new_rps",
|
|
||||||
data = hashMapOf(
|
|
||||||
"id" to face.resultId.ifEmpty { "0" }.toInt()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
394 -> {
|
|
||||||
//LogCenter.log(face.toString())
|
|
||||||
return MessageSegment(
|
|
||||||
type = "face",
|
|
||||||
data = hashMapOf(
|
|
||||||
"id" to face.faceIndex,
|
|
||||||
"big" to (face.faceType == 3),
|
|
||||||
"result" to (face.resultId ?: "1")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else -> return MessageSegment(
|
|
||||||
type = "face",
|
|
||||||
data = hashMapOf(
|
|
||||||
"id" to face.faceIndex,
|
|
||||||
"big" to (face.faceType == 3)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 图片消息转换消息段
|
|
||||||
*/
|
|
||||||
data object ImageConverter: MessageElemConverter() {
|
|
||||||
override suspend fun convert(
|
|
||||||
chatType: Int,
|
|
||||||
peerId: String,
|
|
||||||
subPeer: String,
|
|
||||||
element: MsgElement
|
|
||||||
): MessageSegment {
|
|
||||||
val image = element.picElement
|
|
||||||
val md5 = image.md5HexStr ?: image.fileName
|
|
||||||
.replace("{", "")
|
|
||||||
.replace("}", "")
|
|
||||||
.replace("-", "").split(".")[0]
|
|
||||||
|
|
||||||
ImageDB.getInstance().imageMappingDao().insert(
|
|
||||||
ImageMapping(md5.uppercase(), chatType, image.fileSize)
|
|
||||||
)
|
|
||||||
|
|
||||||
//LogCenter.log(image.toString())
|
|
||||||
|
|
||||||
val originalUrl = image.originImageUrl ?: ""
|
|
||||||
//LogCenter.log({ "receive image: $image" }, Level.DEBUG)
|
|
||||||
|
|
||||||
return MessageSegment(
|
|
||||||
type = "image",
|
|
||||||
data = hashMapOf(
|
|
||||||
"file" to md5,
|
|
||||||
"url" to when(chatType) {
|
|
||||||
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(originalUrl, md5)
|
|
||||||
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(originalUrl, md5)
|
|
||||||
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(originalUrl, md5)
|
|
||||||
else -> unknownChatType(chatType)
|
|
||||||
},
|
|
||||||
"subType" to image.picSubType,
|
|
||||||
"type" to if (image.isFlashPic == true) "flash" else if(image.original) "original" else "show"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 语音消息转换消息段
|
|
||||||
*/
|
|
||||||
data object VoiceConverter: MessageElemConverter() {
|
|
||||||
override suspend fun convert(
|
|
||||||
chatType: Int,
|
|
||||||
peerId: String,
|
|
||||||
subPeer: String,
|
|
||||||
element: MsgElement
|
|
||||||
): MessageSegment {
|
|
||||||
val record = element.pttElement
|
|
||||||
|
|
||||||
val md5 = if (record.fileName.startsWith("silk"))
|
|
||||||
record.fileName.substring(5)
|
|
||||||
else record.md5HexStr
|
|
||||||
|
|
||||||
return MessageSegment(
|
|
||||||
type = "record",
|
|
||||||
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("0", record.md5HexStr, record.fileUuid)
|
|
||||||
else -> unknownChatType(chatType)
|
|
||||||
}
|
|
||||||
).also {
|
|
||||||
if(record.voiceChangeType != MsgConstant.KPTTVOICECHANGETYPENONE) {
|
|
||||||
it["magic"] = "1".json
|
|
||||||
}
|
|
||||||
if ((it["url"] as String).isBlank()) {
|
|
||||||
it.remove("url")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 视频消息转换消息段
|
|
||||||
*/
|
|
||||||
data object VideoConverter: MessageElemConverter() {
|
|
||||||
override suspend fun convert(
|
|
||||||
chatType: Int,
|
|
||||||
peerId: String,
|
|
||||||
subPeer: String,
|
|
||||||
element: MsgElement
|
|
||||||
): MessageSegment {
|
|
||||||
val video = element.videoElement
|
|
||||||
val md5 = if (video.fileName.contains("/")) {
|
|
||||||
video.videoMd5.takeIf {
|
|
||||||
!it.isNullOrEmpty()
|
|
||||||
}?.hex2ByteArray() ?: video.fileName.split("/").let {
|
|
||||||
it[it.size - 2].hex2ByteArray()
|
|
||||||
}
|
|
||||||
} else video.fileName.split(".")[0].hex2ByteArray()
|
|
||||||
|
|
||||||
//LogCenter.log({ "receive video msg: $video" }, Level.DEBUG)
|
|
||||||
|
|
||||||
return MessageSegment(
|
|
||||||
type = "video",
|
|
||||||
data = hashMapOf(
|
|
||||||
"file" to video.fileName,
|
|
||||||
"url" to when(chatType) {
|
|
||||||
MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid)
|
|
||||||
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", md5, video.fileUuid)
|
|
||||||
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid)
|
|
||||||
else -> unknownChatType(chatType)
|
|
||||||
}
|
|
||||||
).also {
|
|
||||||
if ((it["url"] as String).isBlank())
|
|
||||||
it.remove("url")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 商城大表情消息转换消息段
|
|
||||||
*/
|
|
||||||
data object MarketFaceConverter: MessageElemConverter() {
|
|
||||||
override suspend fun convert(
|
|
||||||
chatType: Int,
|
|
||||||
peerId: String,
|
|
||||||
subPeer: String,
|
|
||||||
element: MsgElement
|
|
||||||
): MessageSegment {
|
|
||||||
val face = element.marketFaceElement
|
|
||||||
return when (face.emojiId.lowercase()) {
|
|
||||||
"4823d3adb15df08014ce5d6796b76ee1" -> MessageSegment("dice")
|
|
||||||
"83c8a293ae65ca140f348120a77448ee" -> MessageSegment("rps")
|
|
||||||
else -> MessageSegment(
|
|
||||||
type = "mface",
|
|
||||||
data = hashMapOf(
|
|
||||||
"id" to face.emojiId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON消息转消息段
|
|
||||||
*/
|
|
||||||
data object StructJsonConverter: MessageElemConverter() {
|
|
||||||
override suspend fun convert(
|
|
||||||
chatType: Int,
|
|
||||||
peerId: String,
|
|
||||||
subPeer: String,
|
|
||||||
element: MsgElement
|
|
||||||
): MessageSegment {
|
|
||||||
val data = element.arkElement.bytesData.asJsonObject
|
|
||||||
return when (data["app"].asString) {
|
|
||||||
"com.tencent.multimsg" -> {
|
|
||||||
val info = data["meta"].asJsonObject["detail"].asJsonObject
|
|
||||||
MessageSegment(
|
|
||||||
type = "forward",
|
|
||||||
data = mapOf(
|
|
||||||
"id" to info["resid"].asString
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
"com.tencent.troopsharecard" -> {
|
|
||||||
val info = data["meta"].asJsonObject["contact"].asJsonObject
|
|
||||||
MessageSegment(
|
|
||||||
type = "contact",
|
|
||||||
data = hashMapOf(
|
|
||||||
"type" to "group",
|
|
||||||
"id" to info["jumpUrl"].asString.split("group_code=")[1]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
"com.tencent.contact.lua" -> {
|
|
||||||
val info = data["meta"].asJsonObject["contact"].asJsonObject
|
|
||||||
MessageSegment(
|
|
||||||
type = "contact",
|
|
||||||
data = hashMapOf(
|
|
||||||
"type" to "private",
|
|
||||||
"id" to info["jumpUrl"].asString.split("uin=")[1]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
"com.tencent.map" -> {
|
|
||||||
val info = data["meta"].asJsonObject["Location.Search"].asJsonObject
|
|
||||||
MessageSegment(
|
|
||||||
type = "location",
|
|
||||||
data = hashMapOf(
|
|
||||||
"lat" to info["lat"].asString,
|
|
||||||
"lon" to info["lng"].asString,
|
|
||||||
"content" to info["address"].asString,
|
|
||||||
"title" to info["name"].asString
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else -> MessageSegment(
|
|
||||||
type = "json",
|
|
||||||
data = mapOf(
|
|
||||||
"data" to element.arkElement.bytesData.asJsonObject.toString()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 回复消息转消息段
|
|
||||||
*/
|
|
||||||
data object ReplyConverter: MessageElemConverter() {
|
|
||||||
override suspend fun convert(
|
|
||||||
chatType: Int,
|
|
||||||
peerId: String,
|
|
||||||
subPeer: String,
|
|
||||||
element: MsgElement
|
|
||||||
): MessageSegment {
|
|
||||||
val reply = element.replyElement
|
|
||||||
val msgId = reply.replayMsgId
|
|
||||||
val msgHash = if (msgId != 0L) {
|
|
||||||
MessageHelper.generateMsgIdHash(chatType, msgId)
|
|
||||||
} else {
|
|
||||||
MessageDB.getInstance().messageMappingDao()
|
|
||||||
.queryByMsgSeq(chatType, peerId, reply.replayMsgSeq?.toInt() ?: 0)?.msgHashId
|
|
||||||
?:
|
|
||||||
kotlin.run {
|
|
||||||
LogCenter.log("消息映射关系未找到: Message($reply)", Level.WARN)
|
|
||||||
MessageHelper.generateMsgIdHash(chatType, reply.sourceMsgIdInRecords)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return MessageSegment(
|
|
||||||
type = "reply",
|
|
||||||
data = mapOf(
|
|
||||||
"id" to msgHash
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 灰色提示条消息过滤
|
|
||||||
*/
|
|
||||||
data object GrayTipsConverter: MessageElemConverter() {
|
|
||||||
override suspend fun convert(
|
|
||||||
chatType: Int,
|
|
||||||
peerId: String,
|
|
||||||
subPeer: String,
|
|
||||||
element: MsgElement
|
|
||||||
): MessageSegment {
|
|
||||||
val tip = element.grayTipElement
|
|
||||||
when(tip.subElementType) {
|
|
||||||
MsgConstant.GRAYTIPELEMENTSUBTYPEJSON -> {
|
|
||||||
val notify = tip.jsonGrayTipElement
|
|
||||||
when(notify.busiId) {
|
|
||||||
/* 新人入群 */ 17L, /* 群戳一戳 */1061L,
|
|
||||||
/* 群撤回 */1014L, /* 群设精消息 */2401L,
|
|
||||||
/* 群头衔 */2407L -> {}
|
|
||||||
else -> LogCenter.log("不支持的灰条类型(JSON): ${notify.busiId}", Level.WARN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MsgConstant.GRAYTIPELEMENTSUBTYPEXMLMSG -> {
|
|
||||||
val notify = tip.xmlElement
|
|
||||||
when(notify.busiId) {
|
|
||||||
/* 群戳一戳 */1061L, /* 群打卡 */1068L -> {}
|
|
||||||
else -> LogCenter.log("不支持的灰条类型(XML): ${notify.busiId}", Level.WARN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> LogCenter.log("不支持的提示类型: ${tip.subElementType}", Level.WARN)
|
|
||||||
}
|
|
||||||
// 提示类消息,这里提供的是一个xml,不具备解析通用性
|
|
||||||
// 在这里不推送
|
|
||||||
throw UnknownError()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文件消息转换消息段
|
|
||||||
*/
|
|
||||||
data object FileConverter: MessageElemConverter() {
|
|
||||||
override suspend fun convert(
|
|
||||||
chatType: Int,
|
|
||||||
peerId: String,
|
|
||||||
subPeer: String,
|
|
||||||
element: MsgElement
|
|
||||||
): MessageSegment {
|
|
||||||
val fileMsg = element.fileElement
|
|
||||||
val fileName = fileMsg.fileName
|
|
||||||
val fileSize = fileMsg.fileSize
|
|
||||||
val expireTime = fileMsg.expireTime ?: 0
|
|
||||||
val fileId = fileMsg.fileUuid
|
|
||||||
val bizId = fileMsg.fileBizId ?: 0
|
|
||||||
val fileSubId = fileMsg.fileSubId ?: ""
|
|
||||||
val url = when (chatType) {
|
|
||||||
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId)
|
|
||||||
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildFileDownUrl(peerId, subPeer, fileId, bizId)
|
|
||||||
else -> RichProtoSvc.getGroupFileDownUrl(peerId.toLong(), fileId, bizId)
|
|
||||||
}
|
|
||||||
|
|
||||||
return MessageSegment(
|
|
||||||
type = "file",
|
|
||||||
data = mapOf(
|
|
||||||
"name" to fileName,
|
|
||||||
"size" to fileSize,
|
|
||||||
"expire" to expireTime,
|
|
||||||
"id" to fileId,
|
|
||||||
"url" to url,
|
|
||||||
"biz" to bizId,
|
|
||||||
"sub" to fileSubId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 老板QQ的合并转发信息
|
|
||||||
*/
|
|
||||||
data object XmlMultiMsgConverter: MessageElemConverter() {
|
|
||||||
override suspend fun convert(
|
|
||||||
chatType: Int,
|
|
||||||
peerId: String,
|
|
||||||
subPeer: String,
|
|
||||||
element: MsgElement
|
|
||||||
): MessageSegment {
|
|
||||||
val multiMsg = element.multiForwardMsgElement
|
|
||||||
return MessageSegment(
|
|
||||||
type = "forward",
|
|
||||||
data = mapOf(
|
|
||||||
"id" to multiMsg.resId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data object XmlLongMsgConverter: MessageElemConverter() {
|
|
||||||
override suspend fun convert(
|
|
||||||
chatType: Int,
|
|
||||||
peerId: String,
|
|
||||||
subPeer: String,
|
|
||||||
element: MsgElement
|
|
||||||
): MessageSegment {
|
|
||||||
val longMsg = element.structLongMsgElement
|
|
||||||
return MessageSegment(
|
|
||||||
type = "forward",
|
|
||||||
data = mapOf(
|
|
||||||
"id" to longMsg.resId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data object MarkdownConverter: MessageElemConverter() {
|
|
||||||
override suspend fun convert(
|
|
||||||
chatType: Int,
|
|
||||||
peerId: String,
|
|
||||||
subPeer: String,
|
|
||||||
element: MsgElement
|
|
||||||
): MessageSegment {
|
|
||||||
val markdown = element.markdownElement
|
|
||||||
return MessageSegment(
|
|
||||||
type = "markdown",
|
|
||||||
data = mapOf(
|
|
||||||
"content" to markdown.content
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data object BubbleFaceConverter: MessageElemConverter() {
|
|
||||||
override suspend fun convert(
|
|
||||||
chatType: Int,
|
|
||||||
peerId: String,
|
|
||||||
subPeer: String,
|
|
||||||
element: MsgElement
|
|
||||||
): MessageSegment {
|
|
||||||
val bubbleElement = element.faceBubbleElement
|
|
||||||
return MessageSegment(
|
|
||||||
type = "bubble_face",
|
|
||||||
data = mapOf(
|
|
||||||
"id" to bubbleElement.yellowFaceInfo.index,
|
|
||||||
"count" to (bubbleElement.faceCount ?: 1),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data object InlineKeyboardConverter: MessageElemConverter() {
|
|
||||||
override suspend fun convert(
|
|
||||||
chatType: Int,
|
|
||||||
peerId: String,
|
|
||||||
subPeer: String,
|
|
||||||
element: MsgElement
|
|
||||||
): MessageSegment {
|
|
||||||
val keyboard = element.inlineKeyboardElement
|
|
||||||
return MessageSegment(
|
|
||||||
type = "inline_keyboard",
|
|
||||||
data = mapOf(
|
|
||||||
"data" to buildJsonObject {
|
|
||||||
putJsonArray("rows") {
|
|
||||||
keyboard.rows.forEach { row ->
|
|
||||||
add(buildJsonObject row@{
|
|
||||||
putJsonArray("buttons") {
|
|
||||||
row.buttons.forEach { button ->
|
|
||||||
add(buildJsonObject {
|
|
||||||
put("id", button.id ?: "")
|
|
||||||
put("label", button.label ?: "")
|
|
||||||
put("visited_label", button.visitedLabel ?: "")
|
|
||||||
put("style", button.style)
|
|
||||||
put("type", button.type)
|
|
||||||
put("click_limit", button.clickLimit)
|
|
||||||
put("unsupport_tips", button.unsupportTips ?: "")
|
|
||||||
put("data", button.data)
|
|
||||||
put("at_bot_show_channel_list", button.atBotShowChannelList)
|
|
||||||
put("permission_type", button.permissionType)
|
|
||||||
putJsonArray("specify_role_ids") {
|
|
||||||
button.specifyRoleIds?.forEach { add(it) }
|
|
||||||
}
|
|
||||||
putJsonArray("specify_tinyids") {
|
|
||||||
button.specifyTinyids?.forEach { add(it) }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
put("bot_appid", keyboard.botAppid)
|
|
||||||
}.toString()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun unknownChatType(chatType: Int) {
|
|
||||||
throw UnsupportedOperationException("Not supported chat type: $chatType")
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,608 @@
|
|||||||
|
package moe.fuqiuluo.qqinterface.servlet.msg.messageelement
|
||||||
|
|
||||||
|
import kotlinx.io.core.ByteReadPacket
|
||||||
|
import kotlinx.io.core.discardExact
|
||||||
|
import kotlinx.io.core.readUInt
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.msg.MessageSegment
|
||||||
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
|
import protobuf.message.MessageElement
|
||||||
|
|
||||||
|
|
||||||
|
internal suspend fun List<MessageElement>.toSegments(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
subPeer: String
|
||||||
|
): List<MessageSegment> {
|
||||||
|
val messageData = arrayListOf<MessageSegment>()
|
||||||
|
this.forEach { msg ->
|
||||||
|
kotlin.runCatching {
|
||||||
|
val elementType = if (msg.text != null) {
|
||||||
|
1
|
||||||
|
} else if (msg.face != null) {
|
||||||
|
2
|
||||||
|
} else if (msg.json != null) {
|
||||||
|
51
|
||||||
|
} else
|
||||||
|
throw UnsupportedOperationException("不支持的消息element类型:$msg")
|
||||||
|
val converter = MessageElementConverter[elementType]
|
||||||
|
converter?.invoke(chatType, peerId, subPeer, msg)
|
||||||
|
?: throw UnsupportedOperationException("不支持的消息element类型:$elementType")
|
||||||
|
}.onSuccess {
|
||||||
|
messageData.add(it)
|
||||||
|
}.onFailure {
|
||||||
|
if (it is UnknownError) {
|
||||||
|
// 不处理的消息类型,抛出unknown error
|
||||||
|
} else {
|
||||||
|
LogCenter.log("消息element转换错误:$it", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return messageData
|
||||||
|
}
|
||||||
|
|
||||||
|
internal typealias IMessageElementConverter = suspend (Int, String, String, MessageElement) -> MessageSegment
|
||||||
|
|
||||||
|
internal object MessageElementConverter {
|
||||||
|
private val convertMap = hashMapOf(
|
||||||
|
1 to MessageElementConverter::convertTextElem,
|
||||||
|
// MsgConstant.KELEMTYPEFACE to MessageElementConverter::convertFaceElem,
|
||||||
|
// MsgConstant.KELEMTYPEPIC to MessageElementConverter::convertImageElem,
|
||||||
|
// MsgConstant.KELEMTYPEPTT to MessageElementConverter::convertVoiceElem,
|
||||||
|
// MsgConstant.KELEMTYPEVIDEO to MessageElementConverter::convertVideoElem,
|
||||||
|
// MsgConstant.KELEMTYPEMARKETFACE to MessageElementConverter::convertMarketFaceElem,
|
||||||
|
// MsgConstant.KELEMTYPEARKSTRUCT to MessageElementConverter::convertStructJsonElem,
|
||||||
|
// MsgConstant.KELEMTYPEREPLY to MessageElementConverter::convertReplyElem,
|
||||||
|
// MsgConstant.KELEMTYPEGRAYTIP to MessageElementConverter::convertGrayTipsElem,
|
||||||
|
// MsgConstant.KELEMTYPEFILE to MessageElementConverter::convertFileElem,
|
||||||
|
// MsgConstant.KELEMTYPEMARKDOWN to MessageElementConverter::convertMarkdownElem,
|
||||||
|
// //MsgConstant.KELEMTYPEMULTIFORWARD to MessageElementConverter::convertXmlMultiMsgElem,
|
||||||
|
// //MsgConstant.KELEMTYPESTRUCTLONGMSG to MessageElementConverter::convertXmlLongMsgElem,
|
||||||
|
// MsgConstant.KELEMTYPEFACEBUBBLE to MessageElementConverter::convertBubbleFaceElem,
|
||||||
|
// MsgConstant.KELEMTYPEINLINEKEYBOARD to MessageElementConverter::convertInlineKeyboardElem,
|
||||||
|
)
|
||||||
|
|
||||||
|
operator fun get(type: Int): IMessageElementConverter? = convertMap[type]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文本 / 艾特 消息转换消息段
|
||||||
|
*/
|
||||||
|
private suspend fun convertTextElem(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
subPeer: String,
|
||||||
|
element: MessageElement
|
||||||
|
): MessageSegment {
|
||||||
|
val text = element.text!!
|
||||||
|
if (text.attr6Buf != null) {
|
||||||
|
val at = ByteReadPacket(text.attr6Buf!!)
|
||||||
|
at.discardExact(7)
|
||||||
|
val uin = at.readUInt()
|
||||||
|
return MessageSegment(
|
||||||
|
type = "at",
|
||||||
|
data = hashMapOf(
|
||||||
|
"qq" to uin
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else if (text.pbReserve != null) {
|
||||||
|
val resv = text.pbReserve!!
|
||||||
|
return MessageSegment(
|
||||||
|
type = "at",
|
||||||
|
data = hashMapOf(
|
||||||
|
"qq" to when (resv.atType) {
|
||||||
|
2 -> resv.atMemberTinyid!!
|
||||||
|
4 -> resv.atChannelInfo!!.channelId!!
|
||||||
|
else -> throw UnsupportedOperationException("Unknown at type: ${resv.atType}")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return MessageSegment(
|
||||||
|
type = "text",
|
||||||
|
data = hashMapOf(
|
||||||
|
"text" to text.text!!
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * 小表情 / 戳一戳 消息转换消息段
|
||||||
|
// */
|
||||||
|
// private suspend fun convertFaceElem(
|
||||||
|
// chatType: Int,
|
||||||
|
// peerId: String,
|
||||||
|
// subPeer: String,
|
||||||
|
// element: MessageElement
|
||||||
|
// ): MessageSegment {
|
||||||
|
// val face = element.faceElement
|
||||||
|
//
|
||||||
|
// if (face.faceType == 5) {
|
||||||
|
// return MessageSegment(
|
||||||
|
// type = "poke",
|
||||||
|
// data = hashMapOf(
|
||||||
|
// "type" to face.pokeType,
|
||||||
|
// "id" to face.vaspokeId,
|
||||||
|
// "strength" to face.pokeStrength
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// when (face.faceIndex) {
|
||||||
|
// 114 -> {
|
||||||
|
// return MessageSegment(
|
||||||
|
// type = "basketball",
|
||||||
|
// data = hashMapOf(
|
||||||
|
// "id" to face.resultId.ifEmpty { "0" }.toInt(),
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// 358 -> {
|
||||||
|
// if (face.sourceType == 1) return MessageSegment("new_dice")
|
||||||
|
// return MessageSegment(
|
||||||
|
// type = "new_dice",
|
||||||
|
// data = hashMapOf(
|
||||||
|
// "id" to face.resultId.ifEmpty { "0" }.toInt()
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// 359 -> {
|
||||||
|
// if (face.resultId.isEmpty()) return MessageSegment("new_rps")
|
||||||
|
// return MessageSegment(
|
||||||
|
// type = "new_rps",
|
||||||
|
// data = hashMapOf(
|
||||||
|
// "id" to face.resultId.ifEmpty { "0" }.toInt()
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// 394 -> {
|
||||||
|
// //LogCenter.log(face.toString())
|
||||||
|
// return MessageSegment(
|
||||||
|
// type = "face",
|
||||||
|
// data = hashMapOf(
|
||||||
|
// "id" to face.faceIndex,
|
||||||
|
// "big" to (face.faceType == 3),
|
||||||
|
// "result" to (face.resultId ?: "1")
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// else -> return MessageSegment(
|
||||||
|
// type = "face",
|
||||||
|
// data = hashMapOf(
|
||||||
|
// "id" to face.faceIndex,
|
||||||
|
// "big" to (face.faceType == 3)
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * 图片消息转换消息段
|
||||||
|
// */
|
||||||
|
// private suspend fun convertImageElem(
|
||||||
|
// chatType: Int,
|
||||||
|
// peerId: String,
|
||||||
|
// subPeer: String,
|
||||||
|
// element: MessageElement
|
||||||
|
// ): MessageSegment {
|
||||||
|
// val image = element.picElement
|
||||||
|
// val md5 = image.md5HexStr ?: image.fileName
|
||||||
|
// .replace("{", "")
|
||||||
|
// .replace("}", "")
|
||||||
|
// .replace("-", "").split(".")[0]
|
||||||
|
//
|
||||||
|
// ImageDB.getInstance().imageMappingDao().insert(
|
||||||
|
// ImageMapping(md5.uppercase(), chatType, image.fileSize)
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// //LogCenter.log(image.toString())
|
||||||
|
//
|
||||||
|
// val originalUrl = image.originImageUrl ?: ""
|
||||||
|
// //LogCenter.log({ "receive image: $image" }, Level.DEBUG)
|
||||||
|
//
|
||||||
|
// return MessageSegment(
|
||||||
|
// type = "image",
|
||||||
|
// data = hashMapOf(
|
||||||
|
// "file" to md5,
|
||||||
|
// "url" to when (chatType) {
|
||||||
|
// MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(
|
||||||
|
// originalUrl,
|
||||||
|
// md5
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(originalUrl, md5)
|
||||||
|
// MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(originalUrl, md5)
|
||||||
|
// else -> throw UnsupportedOperationException("Not supported chat type: $chatType")
|
||||||
|
// },
|
||||||
|
// "subType" to image.picSubType,
|
||||||
|
// "type" to if (image.isFlashPic == true) "flash" else if (image.original) "original" else "show"
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * 语音消息转换消息段
|
||||||
|
// */
|
||||||
|
// private suspend fun convertVoiceElem(
|
||||||
|
// chatType: Int,
|
||||||
|
// peerId: String,
|
||||||
|
// subPeer: String,
|
||||||
|
// element: MessageElement
|
||||||
|
// ): MessageSegment {
|
||||||
|
// val record = element.pttElement
|
||||||
|
//
|
||||||
|
// val md5 = if (record.fileName.startsWith("silk"))
|
||||||
|
// record.fileName.substring(5)
|
||||||
|
// else record.md5HexStr
|
||||||
|
//
|
||||||
|
// return MessageSegment(
|
||||||
|
// type = "record",
|
||||||
|
// 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(
|
||||||
|
// "0",
|
||||||
|
// record.md5HexStr,
|
||||||
|
// record.fileUuid
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// else -> throw UnsupportedOperationException("Not supported chat type: $chatType")
|
||||||
|
// }
|
||||||
|
// ).also {
|
||||||
|
// if (record.voiceChangeType != MsgConstant.KPTTVOICECHANGETYPENONE) {
|
||||||
|
// it["magic"] = "1"
|
||||||
|
// }
|
||||||
|
// if ((it["url"] as String).isBlank()) {
|
||||||
|
// it.remove("url")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * 视频消息转换消息段
|
||||||
|
// */
|
||||||
|
// private suspend fun convertVideoElem(
|
||||||
|
// chatType: Int,
|
||||||
|
// peerId: String,
|
||||||
|
// subPeer: String,
|
||||||
|
// element: MessageElement
|
||||||
|
// ): MessageSegment {
|
||||||
|
// val video = element.videoElement
|
||||||
|
// val md5 = if (video.fileName.contains("/")) {
|
||||||
|
// video.videoMd5.takeIf {
|
||||||
|
// !it.isNullOrEmpty()
|
||||||
|
// }?.hex2ByteArray() ?: video.fileName.split("/").let {
|
||||||
|
// it[it.size - 2].hex2ByteArray()
|
||||||
|
// }
|
||||||
|
// } else video.fileName.split(".")[0].hex2ByteArray()
|
||||||
|
//
|
||||||
|
// //LogCenter.log({ "receive video msg: $video" }, Level.DEBUG)
|
||||||
|
//
|
||||||
|
// return MessageSegment(
|
||||||
|
// type = "video",
|
||||||
|
// data = hashMapOf(
|
||||||
|
// "file" to video.fileName,
|
||||||
|
// "url" to when (chatType) {
|
||||||
|
// MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid)
|
||||||
|
// MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", md5, video.fileUuid)
|
||||||
|
// MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid)
|
||||||
|
// else -> throw UnsupportedOperationException("Not supported chat type: $chatType")
|
||||||
|
// }
|
||||||
|
// ).also {
|
||||||
|
// if ((it["url"] as String).isBlank())
|
||||||
|
// it.remove("url")
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * 商城大表情消息转换消息段
|
||||||
|
// */
|
||||||
|
// private suspend fun convertMarketFaceElem(
|
||||||
|
// chatType: Int,
|
||||||
|
// peerId: String,
|
||||||
|
// subPeer: String,
|
||||||
|
// element: MessageElement
|
||||||
|
// ): MessageSegment {
|
||||||
|
// val face = element.marketFaceElement
|
||||||
|
// return when (face.emojiId.lowercase()) {
|
||||||
|
// "4823d3adb15df08014ce5d6796b76ee1" -> MessageSegment("dice")
|
||||||
|
// "83c8a293ae65ca140f348120a77448ee" -> MessageSegment("rps")
|
||||||
|
// else -> MessageSegment(
|
||||||
|
// type = "mface",
|
||||||
|
// data = hashMapOf(
|
||||||
|
// "id" to face.emojiId
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * JSON消息转消息段
|
||||||
|
// */
|
||||||
|
// private suspend fun convertStructJsonElem(
|
||||||
|
// chatType: Int,
|
||||||
|
// peerId: String,
|
||||||
|
// subPeer: String,
|
||||||
|
// element: MessageElement
|
||||||
|
// ): MessageSegment {
|
||||||
|
// val data = element.arkElement.bytesData.asJsonObject
|
||||||
|
// return when (data["app"].asString) {
|
||||||
|
// "com.tencent.multimsg" -> {
|
||||||
|
// val info = data["meta"].asJsonObject["detail"].asJsonObject
|
||||||
|
// MessageSegment(
|
||||||
|
// type = "forward",
|
||||||
|
// data = mapOf(
|
||||||
|
// "id" to info["resid"].asString
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// "com.tencent.troopsharecard" -> {
|
||||||
|
// val info = data["meta"].asJsonObject["contact"].asJsonObject
|
||||||
|
// MessageSegment(
|
||||||
|
// type = "contact",
|
||||||
|
// data = hashMapOf(
|
||||||
|
// "type" to "group",
|
||||||
|
// "id" to info["jumpUrl"].asString.split("group_code=")[1]
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// "com.tencent.contact.lua" -> {
|
||||||
|
// val info = data["meta"].asJsonObject["contact"].asJsonObject
|
||||||
|
// MessageSegment(
|
||||||
|
// type = "contact",
|
||||||
|
// data = hashMapOf(
|
||||||
|
// "type" to "private",
|
||||||
|
// "id" to info["jumpUrl"].asString.split("uin=")[1]
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// "com.tencent.map" -> {
|
||||||
|
// val info = data["meta"].asJsonObject["Location.Search"].asJsonObject
|
||||||
|
// MessageSegment(
|
||||||
|
// type = "location",
|
||||||
|
// data = hashMapOf(
|
||||||
|
// "lat" to info["lat"].asString,
|
||||||
|
// "lon" to info["lng"].asString,
|
||||||
|
// "content" to info["address"].asString,
|
||||||
|
// "title" to info["name"].asString
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// else -> MessageSegment(
|
||||||
|
// type = "json",
|
||||||
|
// data = mapOf(
|
||||||
|
// "data" to element.arkElement.bytesData.asJsonObject.toString()
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * 回复消息转消息段
|
||||||
|
// */
|
||||||
|
// private suspend fun convertReplyElem(
|
||||||
|
// chatType: Int,
|
||||||
|
// peerId: String,
|
||||||
|
// subPeer: String,
|
||||||
|
// element: MessageElement
|
||||||
|
// ): MessageSegment {
|
||||||
|
// val reply = element.replyElement
|
||||||
|
// val msgId = reply.replayMsgId
|
||||||
|
// val msgHash = if (msgId != 0L) {
|
||||||
|
// MessageHelper.generateMsgIdHash(chatType, msgId)
|
||||||
|
// } else {
|
||||||
|
// MessageDB.getInstance().messageMappingDao()
|
||||||
|
// .queryByMsgSeq(chatType, peerId, reply.replayMsgSeq?.toInt() ?: 0)?.msgHashId
|
||||||
|
// ?: kotlin.run {
|
||||||
|
// LogCenter.log("消息映射关系未找到: Message($reply)", Level.WARN)
|
||||||
|
// MessageHelper.generateMsgIdHash(chatType, reply.sourceMsgIdInRecords)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return MessageSegment(
|
||||||
|
// type = "reply",
|
||||||
|
// data = mapOf(
|
||||||
|
// "id" to msgHash
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * 灰色提示条消息过滤
|
||||||
|
// */
|
||||||
|
// private suspend fun convertGrayTipsElem(
|
||||||
|
// chatType: Int,
|
||||||
|
// peerId: String,
|
||||||
|
// subPeer: String,
|
||||||
|
// element: MessageElement
|
||||||
|
// ): MessageSegment {
|
||||||
|
// val tip = element.grayTipElement
|
||||||
|
// when (tip.subElementType) {
|
||||||
|
// MsgConstant.GRAYTIPELEMENTSUBTYPEJSON -> {
|
||||||
|
// val notify = tip.jsonGrayTipElement
|
||||||
|
// when (notify.busiId) {
|
||||||
|
// /* 新人入群 */ 17L, /* 群戳一戳 */1061L,
|
||||||
|
// /* 群撤回 */1014L, /* 群设精消息 */2401L,
|
||||||
|
// /* 群头衔 */2407L -> {
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// else -> LogCenter.log("不支持的灰条类型(JSON): ${notify.busiId}", Level.WARN)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// MsgConstant.GRAYTIPELEMENTSUBTYPEXMLMSG -> {
|
||||||
|
// val notify = tip.xmlElement
|
||||||
|
// when (notify.busiId) {
|
||||||
|
// /* 群戳一戳 */1061L, /* 群打卡 */1068L -> {}
|
||||||
|
// else -> LogCenter.log("不支持的灰条类型(XML): ${notify.busiId}", Level.WARN)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// else -> LogCenter.log("不支持的提示类型: ${tip.subElementType}", Level.WARN)
|
||||||
|
// }
|
||||||
|
// // 提示类消息,这里提供的是一个xml,不具备解析通用性
|
||||||
|
// // 在这里不推送
|
||||||
|
// throw UnknownError()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * 文件消息转换消息段
|
||||||
|
// */
|
||||||
|
// private suspend fun convertFileElem(
|
||||||
|
// chatType: Int,
|
||||||
|
// peerId: String,
|
||||||
|
// subPeer: String,
|
||||||
|
// element: MessageElement
|
||||||
|
// ): MessageSegment {
|
||||||
|
// val fileMsg = element.fileElement
|
||||||
|
// val fileName = fileMsg.fileName
|
||||||
|
// val fileSize = fileMsg.fileSize
|
||||||
|
// val expireTime = fileMsg.expireTime ?: 0
|
||||||
|
// val fileId = fileMsg.fileUuid
|
||||||
|
// val bizId = fileMsg.fileBizId ?: 0
|
||||||
|
// val fileSubId = fileMsg.fileSubId ?: ""
|
||||||
|
// val url = when (chatType) {
|
||||||
|
// MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId)
|
||||||
|
// MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildFileDownUrl(peerId, subPeer, fileId, bizId)
|
||||||
|
// else -> RichProtoSvc.getGroupFileDownUrl(peerId.toLong(), fileId, bizId)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return MessageSegment(
|
||||||
|
// type = "file",
|
||||||
|
// data = mapOf(
|
||||||
|
// "name" to fileName,
|
||||||
|
// "size" to fileSize,
|
||||||
|
// "expire" to expireTime,
|
||||||
|
// "id" to fileId,
|
||||||
|
// "url" to url,
|
||||||
|
// "biz" to bizId,
|
||||||
|
// "sub" to fileSubId
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * 老板QQ的合并转发信息
|
||||||
|
// */
|
||||||
|
// private suspend fun convertXmlMultiMsgElem(
|
||||||
|
// chatType: Int,
|
||||||
|
// peerId: String,
|
||||||
|
// subPeer: String,
|
||||||
|
// element: MessageElement
|
||||||
|
// ): MessageSegment {
|
||||||
|
// val multiMsg = element.multiForwardMessageElement
|
||||||
|
// return MessageSegment(
|
||||||
|
// type = "forward",
|
||||||
|
// data = mapOf(
|
||||||
|
// "id" to multiMsg.resId
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private suspend fun convertXmlLongMsgElem(
|
||||||
|
// chatType: Int,
|
||||||
|
// peerId: String,
|
||||||
|
// subPeer: String,
|
||||||
|
// element: MessageElement
|
||||||
|
// ): MessageSegment {
|
||||||
|
// val longMsg = element.structLongMessageElement
|
||||||
|
// return MessageSegment(
|
||||||
|
// type = "forward",
|
||||||
|
// data = mapOf(
|
||||||
|
// "id" to longMsg.resId
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private suspend fun convertMarkdownElem(
|
||||||
|
// chatType: Int,
|
||||||
|
// peerId: String,
|
||||||
|
// subPeer: String,
|
||||||
|
// element: MessageElement
|
||||||
|
// ): MessageSegment {
|
||||||
|
// val markdown = element.markdownElement
|
||||||
|
// return MessageSegment(
|
||||||
|
// type = "markdown",
|
||||||
|
// data = mapOf(
|
||||||
|
// "content" to markdown.content
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private suspend fun convertBubbleFaceElem(
|
||||||
|
// chatType: Int,
|
||||||
|
// peerId: String,
|
||||||
|
// subPeer: String,
|
||||||
|
// element: MessageElement
|
||||||
|
// ): MessageSegment {
|
||||||
|
// val bubbleElement = element.faceBubbleElement
|
||||||
|
// return MessageSegment(
|
||||||
|
// type = "bubble_face",
|
||||||
|
// data = mapOf(
|
||||||
|
// "id" to bubbleElement.yellowFaceInfo.index,
|
||||||
|
// "count" to (bubbleElement.faceCount ?: 1),
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private suspend fun convertInlineKeyboardElem(
|
||||||
|
// chatType: Int,
|
||||||
|
// peerId: String,
|
||||||
|
// subPeer: String,
|
||||||
|
// element: MessageElement
|
||||||
|
// ): MessageSegment {
|
||||||
|
// val keyboard = element.inlineKeyboardElement
|
||||||
|
// return MessageSegment(
|
||||||
|
// type = "inline_keyboard",
|
||||||
|
// data = mapOf(
|
||||||
|
// "data" to buildJsonObject {
|
||||||
|
// putJsonArray("rows") {
|
||||||
|
// keyboard.rows.forEach { row ->
|
||||||
|
// add(buildJsonObject row@{
|
||||||
|
// putJsonArray("buttons") {
|
||||||
|
// row.buttons.forEach { button ->
|
||||||
|
// add(buildJsonObject {
|
||||||
|
// put("id", button.id ?: "")
|
||||||
|
// put("label", button.label ?: "")
|
||||||
|
// put("visited_label", button.visitedLabel ?: "")
|
||||||
|
// put("style", button.style)
|
||||||
|
// put("type", button.type)
|
||||||
|
// put("click_limit", button.clickLimit)
|
||||||
|
// put("unsupport_tips", button.unsupportTips ?: "")
|
||||||
|
// put("data", button.data)
|
||||||
|
// put("at_bot_show_channel_list", button.atBotShowChannelList)
|
||||||
|
// put("permission_type", button.permissionType)
|
||||||
|
// putJsonArray("specify_role_ids") {
|
||||||
|
// button.specifyRoleIds?.forEach { add(it) }
|
||||||
|
// }
|
||||||
|
// putJsonArray("specify_tinyids") {
|
||||||
|
// button.specifyTinyids?.forEach { add(it) }
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// put("bot_appid", keyboard.botAppid)
|
||||||
|
// }.toString()
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package moe.fuqiuluo.qqinterface.servlet.msg
|
package moe.fuqiuluo.qqinterface.servlet.msg.messageelement
|
||||||
|
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import moe.fuqiuluo.shamrock.helper.ParamsException
|
import moe.fuqiuluo.shamrock.helper.ParamsException
|
||||||
@ -7,10 +7,10 @@ import moe.fuqiuluo.shamrock.tools.asString
|
|||||||
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
||||||
import protobuf.message.MessageElement
|
import protobuf.message.MessageElement
|
||||||
import protobuf.message.element.FaceElement
|
import protobuf.message.element.FaceElement
|
||||||
|
import protobuf.message.element.JsonElement
|
||||||
import protobuf.message.element.TextElement
|
import protobuf.message.element.TextElement
|
||||||
|
|
||||||
|
internal typealias IMessageElementMaker = suspend (Int, Long, String, JsonObject) -> Result<MessageElement>
|
||||||
internal typealias IMessageMaker = suspend (Int, Long, String, JsonObject) -> Result<MessageElement>
|
|
||||||
|
|
||||||
internal object MessageElementMaker {
|
internal object MessageElementMaker {
|
||||||
private val makerArray = hashMapOf(
|
private val makerArray = hashMapOf(
|
||||||
@ -43,6 +43,8 @@ internal object MessageElementMaker {
|
|||||||
//"bubble_face" to MessageElementMaker::createBubbleFaceElem,
|
//"bubble_face" to MessageElementMaker::createBubbleFaceElem,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
operator fun get(type: String): IMessageElementMaker? = makerArray[type]
|
||||||
|
|
||||||
private suspend fun createTextElem(
|
private suspend fun createTextElem(
|
||||||
chatType: Int,
|
chatType: Int,
|
||||||
msgId: Long,
|
msgId: Long,
|
||||||
@ -78,7 +80,7 @@ internal object MessageElementMaker {
|
|||||||
data.checkAndThrow("data")
|
data.checkAndThrow("data")
|
||||||
|
|
||||||
val elem = MessageElement(
|
val elem = MessageElement(
|
||||||
json = protobuf.message.element.JsonElement(
|
json = JsonElement(
|
||||||
data = DeflateTools.compress(data.toString().toByteArray())
|
data = DeflateTools.compress(data.toString().toByteArray())
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -90,7 +92,4 @@ internal object MessageElementMaker {
|
|||||||
if (!containsKey(it)) throw ParamsException(it)
|
if (!containsKey(it)) throw ParamsException(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun get(type: String): IMessageMaker? = makerArray[type]
|
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,603 @@
|
|||||||
|
package moe.fuqiuluo.qqinterface.servlet.msg.msgelement
|
||||||
|
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
||||||
|
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
||||||
|
import kotlinx.serialization.json.*
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.msg.MessageSegment
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc
|
||||||
|
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.db.ImageDB
|
||||||
|
import moe.fuqiuluo.shamrock.helper.db.ImageMapping
|
||||||
|
import moe.fuqiuluo.shamrock.helper.db.MessageDB
|
||||||
|
import moe.fuqiuluo.shamrock.tools.asJsonObject
|
||||||
|
import moe.fuqiuluo.shamrock.tools.asString
|
||||||
|
import moe.fuqiuluo.shamrock.tools.hex2ByteArray
|
||||||
|
|
||||||
|
|
||||||
|
internal suspend fun List<MsgElement>.toSegments(chatType: Int, peerId: String, subPeer: String): List<MessageSegment> {
|
||||||
|
val messageData = arrayListOf<MessageSegment>()
|
||||||
|
this.forEach { msg ->
|
||||||
|
kotlin.runCatching {
|
||||||
|
val converter = MsgElementConverter[msg.elementType]
|
||||||
|
converter?.invoke(chatType, peerId, subPeer, msg)
|
||||||
|
?: throw UnsupportedOperationException("不支持的消息element类型:${msg.elementType}")
|
||||||
|
}.onSuccess {
|
||||||
|
messageData.add(it)
|
||||||
|
}.onFailure {
|
||||||
|
if (it is UnknownError) {
|
||||||
|
// 不处理的消息类型,抛出unknown error
|
||||||
|
} else {
|
||||||
|
LogCenter.log("消息element转换错误:$it, elementType: ${msg.elementType}", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return messageData
|
||||||
|
}
|
||||||
|
|
||||||
|
internal suspend fun List<MsgElement>.toCQCode(chatType: Int, peerId: String, subPeer: String): String {
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return MessageHelper.nativeEncodeCQCode(this.toSegments(chatType, peerId, subPeer).map {
|
||||||
|
val params = hashMapOf<String, String>()
|
||||||
|
params["_type"] = it.type
|
||||||
|
it.data.forEach { (key, value) ->
|
||||||
|
params[key] = value.toString()
|
||||||
|
}
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
internal typealias IMsgElementConverter = suspend (Int, String, String, MsgElement) -> MessageSegment
|
||||||
|
|
||||||
|
internal object MsgElementConverter {
|
||||||
|
private val convertMap = hashMapOf(
|
||||||
|
MsgConstant.KELEMTYPETEXT to MsgElementConverter::convertTextElem,
|
||||||
|
MsgConstant.KELEMTYPEFACE to MsgElementConverter::convertFaceElem,
|
||||||
|
MsgConstant.KELEMTYPEPIC to MsgElementConverter::convertImageElem,
|
||||||
|
MsgConstant.KELEMTYPEPTT to MsgElementConverter::convertVoiceElem,
|
||||||
|
MsgConstant.KELEMTYPEVIDEO to MsgElementConverter::convertVideoElem,
|
||||||
|
MsgConstant.KELEMTYPEMARKETFACE to MsgElementConverter::convertMarketFaceElem,
|
||||||
|
MsgConstant.KELEMTYPEARKSTRUCT to MsgElementConverter::convertStructJsonElem,
|
||||||
|
MsgConstant.KELEMTYPEREPLY to MsgElementConverter::convertReplyElem,
|
||||||
|
MsgConstant.KELEMTYPEGRAYTIP to MsgElementConverter::convertGrayTipsElem,
|
||||||
|
MsgConstant.KELEMTYPEFILE to MsgElementConverter::convertFileElem,
|
||||||
|
MsgConstant.KELEMTYPEMARKDOWN to MsgElementConverter::convertMarkdownElem,
|
||||||
|
//MsgConstant.KELEMTYPEMULTIFORWARD to MsgElementConverter::convertXmlMultiMsgElem,
|
||||||
|
//MsgConstant.KELEMTYPESTRUCTLONGMSG to MsgElementConverter::convertXmlLongMsgElem,
|
||||||
|
MsgConstant.KELEMTYPEFACEBUBBLE to MsgElementConverter::convertBubbleFaceElem,
|
||||||
|
MsgConstant.KELEMTYPEINLINEKEYBOARD to MsgElementConverter::convertInlineKeyboardElem,
|
||||||
|
)
|
||||||
|
|
||||||
|
operator fun get(type: Int): IMsgElementConverter? = convertMap[type]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文本 / 艾特 消息转换消息段
|
||||||
|
*/
|
||||||
|
private suspend fun convertTextElem(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
subPeer: String,
|
||||||
|
element: MsgElement
|
||||||
|
): MessageSegment {
|
||||||
|
val text = element.textElement
|
||||||
|
return if (text.atType != MsgConstant.ATTYPEUNKNOWN) {
|
||||||
|
MessageSegment(
|
||||||
|
type = "at",
|
||||||
|
data = hashMapOf(
|
||||||
|
"qq" to ContactHelper.getUinByUidAsync(text.atNtUid),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
MessageSegment(
|
||||||
|
type = "text",
|
||||||
|
data = hashMapOf(
|
||||||
|
"text" to text.content
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小表情 / 戳一戳 消息转换消息段
|
||||||
|
*/
|
||||||
|
private suspend fun convertFaceElem(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
subPeer: String,
|
||||||
|
element: MsgElement
|
||||||
|
): MessageSegment {
|
||||||
|
val face = element.faceElement
|
||||||
|
|
||||||
|
if (face.faceType == 5) {
|
||||||
|
return MessageSegment(
|
||||||
|
type = "poke",
|
||||||
|
data = hashMapOf(
|
||||||
|
"type" to face.pokeType,
|
||||||
|
"id" to face.vaspokeId,
|
||||||
|
"strength" to face.pokeStrength
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
when (face.faceIndex) {
|
||||||
|
114 -> {
|
||||||
|
return MessageSegment(
|
||||||
|
type = "basketball",
|
||||||
|
data = hashMapOf(
|
||||||
|
"id" to face.resultId.ifEmpty { "0" }.toInt(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
358 -> {
|
||||||
|
if (face.sourceType == 1) return MessageSegment("new_dice")
|
||||||
|
return MessageSegment(
|
||||||
|
type = "new_dice",
|
||||||
|
data = hashMapOf(
|
||||||
|
"id" to face.resultId.ifEmpty { "0" }.toInt()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
359 -> {
|
||||||
|
if (face.resultId.isEmpty()) return MessageSegment("new_rps")
|
||||||
|
return MessageSegment(
|
||||||
|
type = "new_rps",
|
||||||
|
data = hashMapOf(
|
||||||
|
"id" to face.resultId.ifEmpty { "0" }.toInt()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
394 -> {
|
||||||
|
//LogCenter.log(face.toString())
|
||||||
|
return MessageSegment(
|
||||||
|
type = "face",
|
||||||
|
data = hashMapOf(
|
||||||
|
"id" to face.faceIndex,
|
||||||
|
"big" to (face.faceType == 3),
|
||||||
|
"result" to (face.resultId ?: "1")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> return MessageSegment(
|
||||||
|
type = "face",
|
||||||
|
data = hashMapOf(
|
||||||
|
"id" to face.faceIndex,
|
||||||
|
"big" to (face.faceType == 3)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片消息转换消息段
|
||||||
|
*/
|
||||||
|
private suspend fun convertImageElem(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
subPeer: String,
|
||||||
|
element: MsgElement
|
||||||
|
): MessageSegment {
|
||||||
|
val image = element.picElement
|
||||||
|
val md5 = image.md5HexStr ?: image.fileName
|
||||||
|
.replace("{", "")
|
||||||
|
.replace("}", "")
|
||||||
|
.replace("-", "").split(".")[0]
|
||||||
|
|
||||||
|
ImageDB.getInstance().imageMappingDao().insert(
|
||||||
|
ImageMapping(md5.uppercase(), chatType, image.fileSize)
|
||||||
|
)
|
||||||
|
|
||||||
|
//LogCenter.log(image.toString())
|
||||||
|
|
||||||
|
val originalUrl = image.originImageUrl ?: ""
|
||||||
|
//LogCenter.log({ "receive image: $image" }, Level.DEBUG)
|
||||||
|
|
||||||
|
return MessageSegment(
|
||||||
|
type = "image",
|
||||||
|
data = hashMapOf(
|
||||||
|
"file" to md5,
|
||||||
|
"url" to when (chatType) {
|
||||||
|
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(
|
||||||
|
originalUrl,
|
||||||
|
md5
|
||||||
|
)
|
||||||
|
|
||||||
|
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(originalUrl, md5)
|
||||||
|
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(originalUrl, md5)
|
||||||
|
else -> throw UnsupportedOperationException("Not supported chat type: $chatType")
|
||||||
|
},
|
||||||
|
"subType" to image.picSubType,
|
||||||
|
"type" to if (image.isFlashPic == true) "flash" else if (image.original) "original" else "show"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语音消息转换消息段
|
||||||
|
*/
|
||||||
|
private suspend fun convertVoiceElem(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
subPeer: String,
|
||||||
|
element: MsgElement
|
||||||
|
): MessageSegment {
|
||||||
|
val record = element.pttElement
|
||||||
|
|
||||||
|
val md5 = if (record.fileName.startsWith("silk"))
|
||||||
|
record.fileName.substring(5)
|
||||||
|
else record.md5HexStr
|
||||||
|
|
||||||
|
return MessageSegment(
|
||||||
|
type = "record",
|
||||||
|
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(
|
||||||
|
"0",
|
||||||
|
record.md5HexStr,
|
||||||
|
record.fileUuid
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> throw UnsupportedOperationException("Not supported chat type: $chatType")
|
||||||
|
}
|
||||||
|
).also {
|
||||||
|
if (record.voiceChangeType != MsgConstant.KPTTVOICECHANGETYPENONE) {
|
||||||
|
it["magic"] = "1"
|
||||||
|
}
|
||||||
|
if ((it["url"] as String).isBlank()) {
|
||||||
|
it.remove("url")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视频消息转换消息段
|
||||||
|
*/
|
||||||
|
private suspend fun convertVideoElem(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
subPeer: String,
|
||||||
|
element: MsgElement
|
||||||
|
): MessageSegment {
|
||||||
|
val video = element.videoElement
|
||||||
|
val md5 = if (video.fileName.contains("/")) {
|
||||||
|
video.videoMd5.takeIf {
|
||||||
|
!it.isNullOrEmpty()
|
||||||
|
}?.hex2ByteArray() ?: video.fileName.split("/").let {
|
||||||
|
it[it.size - 2].hex2ByteArray()
|
||||||
|
}
|
||||||
|
} else video.fileName.split(".")[0].hex2ByteArray()
|
||||||
|
|
||||||
|
//LogCenter.log({ "receive video msg: $video" }, Level.DEBUG)
|
||||||
|
|
||||||
|
return MessageSegment(
|
||||||
|
type = "video",
|
||||||
|
data = hashMapOf(
|
||||||
|
"file" to video.fileName,
|
||||||
|
"url" to when (chatType) {
|
||||||
|
MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid)
|
||||||
|
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", md5, video.fileUuid)
|
||||||
|
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid)
|
||||||
|
else -> throw UnsupportedOperationException("Not supported chat type: $chatType")
|
||||||
|
}
|
||||||
|
).also {
|
||||||
|
if ((it["url"] as String).isBlank())
|
||||||
|
it.remove("url")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商城大表情消息转换消息段
|
||||||
|
*/
|
||||||
|
private suspend fun convertMarketFaceElem(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
subPeer: String,
|
||||||
|
element: MsgElement
|
||||||
|
): MessageSegment {
|
||||||
|
val face = element.marketFaceElement
|
||||||
|
return when (face.emojiId.lowercase()) {
|
||||||
|
"4823d3adb15df08014ce5d6796b76ee1" -> MessageSegment("dice")
|
||||||
|
"83c8a293ae65ca140f348120a77448ee" -> MessageSegment("rps")
|
||||||
|
else -> MessageSegment(
|
||||||
|
type = "mface",
|
||||||
|
data = hashMapOf(
|
||||||
|
"id" to face.emojiId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON消息转消息段
|
||||||
|
*/
|
||||||
|
private suspend fun convertStructJsonElem(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
subPeer: String,
|
||||||
|
element: MsgElement
|
||||||
|
): MessageSegment {
|
||||||
|
val data = element.arkElement.bytesData.asJsonObject
|
||||||
|
return when (data["app"].asString) {
|
||||||
|
"com.tencent.multimsg" -> {
|
||||||
|
val info = data["meta"].asJsonObject["detail"].asJsonObject
|
||||||
|
MessageSegment(
|
||||||
|
type = "forward",
|
||||||
|
data = mapOf(
|
||||||
|
"id" to info["resid"].asString
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
"com.tencent.troopsharecard" -> {
|
||||||
|
val info = data["meta"].asJsonObject["contact"].asJsonObject
|
||||||
|
MessageSegment(
|
||||||
|
type = "contact",
|
||||||
|
data = hashMapOf(
|
||||||
|
"type" to "group",
|
||||||
|
"id" to info["jumpUrl"].asString.split("group_code=")[1]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
"com.tencent.contact.lua" -> {
|
||||||
|
val info = data["meta"].asJsonObject["contact"].asJsonObject
|
||||||
|
MessageSegment(
|
||||||
|
type = "contact",
|
||||||
|
data = hashMapOf(
|
||||||
|
"type" to "private",
|
||||||
|
"id" to info["jumpUrl"].asString.split("uin=")[1]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
"com.tencent.map" -> {
|
||||||
|
val info = data["meta"].asJsonObject["Location.Search"].asJsonObject
|
||||||
|
MessageSegment(
|
||||||
|
type = "location",
|
||||||
|
data = hashMapOf(
|
||||||
|
"lat" to info["lat"].asString,
|
||||||
|
"lon" to info["lng"].asString,
|
||||||
|
"content" to info["address"].asString,
|
||||||
|
"title" to info["name"].asString
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> MessageSegment(
|
||||||
|
type = "json",
|
||||||
|
data = mapOf(
|
||||||
|
"data" to element.arkElement.bytesData.asJsonObject.toString()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回复消息转消息段
|
||||||
|
*/
|
||||||
|
private suspend fun convertReplyElem(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
subPeer: String,
|
||||||
|
element: MsgElement
|
||||||
|
): MessageSegment {
|
||||||
|
val reply = element.replyElement
|
||||||
|
val msgId = reply.replayMsgId
|
||||||
|
val msgHash = if (msgId != 0L) {
|
||||||
|
MessageHelper.generateMsgIdHash(chatType, msgId)
|
||||||
|
} else {
|
||||||
|
MessageDB.getInstance().messageMappingDao()
|
||||||
|
.queryByMsgSeq(chatType, peerId, reply.replayMsgSeq?.toInt() ?: 0)?.msgHashId
|
||||||
|
?: kotlin.run {
|
||||||
|
LogCenter.log("消息映射关系未找到: Message($reply)", Level.WARN)
|
||||||
|
MessageHelper.generateMsgIdHash(chatType, reply.sourceMsgIdInRecords)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return MessageSegment(
|
||||||
|
type = "reply",
|
||||||
|
data = mapOf(
|
||||||
|
"id" to msgHash
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 灰色提示条消息过滤
|
||||||
|
*/
|
||||||
|
private suspend fun convertGrayTipsElem(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
subPeer: String,
|
||||||
|
element: MsgElement
|
||||||
|
): MessageSegment {
|
||||||
|
val tip = element.grayTipElement
|
||||||
|
when (tip.subElementType) {
|
||||||
|
MsgConstant.GRAYTIPELEMENTSUBTYPEJSON -> {
|
||||||
|
val notify = tip.jsonGrayTipElement
|
||||||
|
when (notify.busiId) {
|
||||||
|
/* 新人入群 */ 17L, /* 群戳一戳 */1061L,
|
||||||
|
/* 群撤回 */1014L, /* 群设精消息 */2401L,
|
||||||
|
/* 群头衔 */2407L -> {
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> LogCenter.log("不支持的灰条类型(JSON): ${notify.busiId}", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MsgConstant.GRAYTIPELEMENTSUBTYPEXMLMSG -> {
|
||||||
|
val notify = tip.xmlElement
|
||||||
|
when (notify.busiId) {
|
||||||
|
/* 群戳一戳 */1061L, /* 群打卡 */1068L -> {}
|
||||||
|
else -> LogCenter.log("不支持的灰条类型(XML): ${notify.busiId}", Level.WARN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> LogCenter.log("不支持的提示类型: ${tip.subElementType}", Level.WARN)
|
||||||
|
}
|
||||||
|
// 提示类消息,这里提供的是一个xml,不具备解析通用性
|
||||||
|
// 在这里不推送
|
||||||
|
throw UnknownError()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件消息转换消息段
|
||||||
|
*/
|
||||||
|
private suspend fun convertFileElem(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
subPeer: String,
|
||||||
|
element: MsgElement
|
||||||
|
): MessageSegment {
|
||||||
|
val fileMsg = element.fileElement
|
||||||
|
val fileName = fileMsg.fileName
|
||||||
|
val fileSize = fileMsg.fileSize
|
||||||
|
val expireTime = fileMsg.expireTime ?: 0
|
||||||
|
val fileId = fileMsg.fileUuid
|
||||||
|
val bizId = fileMsg.fileBizId ?: 0
|
||||||
|
val fileSubId = fileMsg.fileSubId ?: ""
|
||||||
|
val url = when (chatType) {
|
||||||
|
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CFileDownUrl(fileId, fileSubId)
|
||||||
|
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildFileDownUrl(peerId, subPeer, fileId, bizId)
|
||||||
|
else -> RichProtoSvc.getGroupFileDownUrl(peerId.toLong(), fileId, bizId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return MessageSegment(
|
||||||
|
type = "file",
|
||||||
|
data = mapOf(
|
||||||
|
"name" to fileName,
|
||||||
|
"size" to fileSize,
|
||||||
|
"expire" to expireTime,
|
||||||
|
"id" to fileId,
|
||||||
|
"url" to url,
|
||||||
|
"biz" to bizId,
|
||||||
|
"sub" to fileSubId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 老板QQ的合并转发信息
|
||||||
|
*/
|
||||||
|
private suspend fun convertXmlMultiMsgElem(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
subPeer: String,
|
||||||
|
element: MsgElement
|
||||||
|
): MessageSegment {
|
||||||
|
val multiMsg = element.multiForwardMsgElement
|
||||||
|
return MessageSegment(
|
||||||
|
type = "forward",
|
||||||
|
data = mapOf(
|
||||||
|
"id" to multiMsg.resId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun convertXmlLongMsgElem(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
subPeer: String,
|
||||||
|
element: MsgElement
|
||||||
|
): MessageSegment {
|
||||||
|
val longMsg = element.structLongMsgElement
|
||||||
|
return MessageSegment(
|
||||||
|
type = "forward",
|
||||||
|
data = mapOf(
|
||||||
|
"id" to longMsg.resId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun convertMarkdownElem(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
subPeer: String,
|
||||||
|
element: MsgElement
|
||||||
|
): MessageSegment {
|
||||||
|
val markdown = element.markdownElement
|
||||||
|
return MessageSegment(
|
||||||
|
type = "markdown",
|
||||||
|
data = mapOf(
|
||||||
|
"content" to markdown.content
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun convertBubbleFaceElem(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
subPeer: String,
|
||||||
|
element: MsgElement
|
||||||
|
): MessageSegment {
|
||||||
|
val bubbleElement = element.faceBubbleElement
|
||||||
|
return MessageSegment(
|
||||||
|
type = "bubble_face",
|
||||||
|
data = mapOf(
|
||||||
|
"id" to bubbleElement.yellowFaceInfo.index,
|
||||||
|
"count" to (bubbleElement.faceCount ?: 1),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun convertInlineKeyboardElem(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
subPeer: String,
|
||||||
|
element: MsgElement
|
||||||
|
): MessageSegment {
|
||||||
|
val keyboard = element.inlineKeyboardElement
|
||||||
|
return MessageSegment(
|
||||||
|
type = "inline_keyboard",
|
||||||
|
data = mapOf(
|
||||||
|
"data" to buildJsonObject {
|
||||||
|
putJsonArray("rows") {
|
||||||
|
keyboard.rows.forEach { row ->
|
||||||
|
add(buildJsonObject row@{
|
||||||
|
putJsonArray("buttons") {
|
||||||
|
row.buttons.forEach { button ->
|
||||||
|
add(buildJsonObject {
|
||||||
|
put("id", button.id ?: "")
|
||||||
|
put("label", button.label ?: "")
|
||||||
|
put("visited_label", button.visitedLabel ?: "")
|
||||||
|
put("style", button.style)
|
||||||
|
put("type", button.type)
|
||||||
|
put("click_limit", button.clickLimit)
|
||||||
|
put("unsupport_tips", button.unsupportTips ?: "")
|
||||||
|
put("data", button.data)
|
||||||
|
put("at_bot_show_channel_list", button.atBotShowChannelList)
|
||||||
|
put("permission_type", button.permissionType)
|
||||||
|
putJsonArray("specify_role_ids") {
|
||||||
|
button.specifyRoleIds?.forEach { add(it) }
|
||||||
|
}
|
||||||
|
putJsonArray("specify_tinyids") {
|
||||||
|
button.specifyTinyids?.forEach { add(it) }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
put("bot_appid", keyboard.botAppid)
|
||||||
|
}.toString()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package moe.fuqiuluo.qqinterface.servlet.msg
|
package moe.fuqiuluo.qqinterface.servlet.msg.msgelement
|
||||||
|
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterface
|
||||||
@ -8,25 +8,7 @@ import com.tencent.mobileqq.pb.ByteStringMicro
|
|||||||
import com.tencent.mobileqq.qroute.QRoute
|
import com.tencent.mobileqq.qroute.QRoute
|
||||||
import com.tencent.qphone.base.remote.ToServiceMsg
|
import com.tencent.qphone.base.remote.ToServiceMsg
|
||||||
import com.tencent.qqnt.aio.adapter.api.IAIOPttApi
|
import com.tencent.qqnt.aio.adapter.api.IAIOPttApi
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.ArkElement
|
import com.tencent.qqnt.kernel.nativeinterface.*
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.FaceBubbleElement
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.FaceElement
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.InlineKeyboardButton
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.InlineKeyboardElement
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.InlineKeyboardRow
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MarkdownElement
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MarketFaceElement
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MarketFaceSupportSize
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.PicElement
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.PttElement
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.QQNTWrapperUtil
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.ReplyElement
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.RichMediaFilePathInfo
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.SmallYellowFaceInfo
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.TextElement
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.VideoElement
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
@ -37,6 +19,7 @@ import moe.fuqiuluo.qqinterface.servlet.LbsSvc
|
|||||||
import moe.fuqiuluo.qqinterface.servlet.ark.ArkAppInfo
|
import moe.fuqiuluo.qqinterface.servlet.ark.ArkAppInfo
|
||||||
import moe.fuqiuluo.qqinterface.servlet.ark.ArkMsgSvc
|
import moe.fuqiuluo.qqinterface.servlet.ark.ArkMsgSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.ark.WeatherSvc
|
import moe.fuqiuluo.qqinterface.servlet.ark.WeatherSvc
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.transfile.*
|
||||||
import moe.fuqiuluo.qqinterface.servlet.transfile.FileTransfer
|
import moe.fuqiuluo.qqinterface.servlet.transfile.FileTransfer
|
||||||
import moe.fuqiuluo.qqinterface.servlet.transfile.PictureResource
|
import moe.fuqiuluo.qqinterface.servlet.transfile.PictureResource
|
||||||
import moe.fuqiuluo.qqinterface.servlet.transfile.Private
|
import moe.fuqiuluo.qqinterface.servlet.transfile.Private
|
||||||
@ -44,8 +27,6 @@ import moe.fuqiuluo.qqinterface.servlet.transfile.Transfer
|
|||||||
import moe.fuqiuluo.qqinterface.servlet.transfile.Troop
|
import moe.fuqiuluo.qqinterface.servlet.transfile.Troop
|
||||||
import moe.fuqiuluo.qqinterface.servlet.transfile.VideoResource
|
import moe.fuqiuluo.qqinterface.servlet.transfile.VideoResource
|
||||||
import moe.fuqiuluo.qqinterface.servlet.transfile.VoiceResource
|
import moe.fuqiuluo.qqinterface.servlet.transfile.VoiceResource
|
||||||
import moe.fuqiuluo.qqinterface.servlet.transfile.trans
|
|
||||||
import moe.fuqiuluo.qqinterface.servlet.transfile.with
|
|
||||||
import moe.fuqiuluo.shamrock.helper.ActionMsgException
|
import moe.fuqiuluo.shamrock.helper.ActionMsgException
|
||||||
import moe.fuqiuluo.shamrock.helper.ContactHelper
|
import moe.fuqiuluo.shamrock.helper.ContactHelper
|
||||||
import moe.fuqiuluo.shamrock.helper.IllegalParamsException
|
import moe.fuqiuluo.shamrock.helper.IllegalParamsException
|
||||||
@ -56,16 +37,7 @@ import moe.fuqiuluo.shamrock.helper.LogicException
|
|||||||
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
||||||
import moe.fuqiuluo.shamrock.helper.MusicHelper
|
import moe.fuqiuluo.shamrock.helper.MusicHelper
|
||||||
import moe.fuqiuluo.shamrock.helper.ParamsException
|
import moe.fuqiuluo.shamrock.helper.ParamsException
|
||||||
import moe.fuqiuluo.shamrock.tools.asBoolean
|
import moe.fuqiuluo.shamrock.tools.*
|
||||||
import moe.fuqiuluo.shamrock.tools.asBooleanOrNull
|
|
||||||
import moe.fuqiuluo.shamrock.tools.asInt
|
|
||||||
import moe.fuqiuluo.shamrock.tools.asIntOrNull
|
|
||||||
import moe.fuqiuluo.shamrock.tools.asJsonArray
|
|
||||||
import moe.fuqiuluo.shamrock.tools.asJsonObject
|
|
||||||
import moe.fuqiuluo.shamrock.tools.asLong
|
|
||||||
import moe.fuqiuluo.shamrock.tools.asString
|
|
||||||
import moe.fuqiuluo.shamrock.tools.asStringOrNull
|
|
||||||
import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
|
|
||||||
import moe.fuqiuluo.shamrock.utils.AudioUtils
|
import moe.fuqiuluo.shamrock.utils.AudioUtils
|
||||||
import moe.fuqiuluo.shamrock.utils.FileUtils
|
import moe.fuqiuluo.shamrock.utils.FileUtils
|
||||||
import moe.fuqiuluo.shamrock.utils.MediaType
|
import moe.fuqiuluo.shamrock.utils.MediaType
|
||||||
@ -82,10 +54,10 @@ import kotlin.math.roundToInt
|
|||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
import kotlin.random.nextInt
|
import kotlin.random.nextInt
|
||||||
|
|
||||||
internal typealias IMsgMaker = suspend (Int, Long, String, JsonObject) -> Result<MsgElement>
|
internal typealias IMsgElementMaker = suspend (Int, Long, String, JsonObject) -> Result<MsgElement>
|
||||||
|
|
||||||
internal object MsgElementMaker {
|
internal object MsgElementMaker {
|
||||||
private val makerArray = hashMapOf(
|
private val makerMap = hashMapOf(
|
||||||
"text" to MsgElementMaker::createTextElem,
|
"text" to MsgElementMaker::createTextElem,
|
||||||
"face" to MsgElementMaker::createFaceElem,
|
"face" to MsgElementMaker::createFaceElem,
|
||||||
"pic" to MsgElementMaker::createImageElem,
|
"pic" to MsgElementMaker::createImageElem,
|
||||||
@ -116,6 +88,8 @@ internal object MsgElementMaker {
|
|||||||
"inline_keyboard" to MsgElementMaker::createInlineKeywordElem
|
"inline_keyboard" to MsgElementMaker::createInlineKeywordElem
|
||||||
)
|
)
|
||||||
|
|
||||||
|
operator fun get(type: String): IMsgElementMaker? = makerMap[type]
|
||||||
|
|
||||||
private suspend fun createInlineKeywordElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> {
|
private suspend fun createInlineKeywordElem(chatType: Int, msgId: Long, peerId: String, data: JsonObject): Result<MsgElement> {
|
||||||
fun tryNewKeyboardButton(btn: JsonObject): InlineKeyboardButton {
|
fun tryNewKeyboardButton(btn: JsonObject): InlineKeyboardButton {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
@ -1028,6 +1002,4 @@ internal object MsgElementMaker {
|
|||||||
if (!containsKey(it)) throw ParamsException(it)
|
if (!containsKey(it)) throw ParamsException(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun get(type: String): IMsgMaker? = makerArray[type]
|
|
||||||
}
|
}
|
@ -16,13 +16,12 @@ import kotlinx.serialization.json.JsonElement
|
|||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.msg.MessageElementMaker
|
import moe.fuqiuluo.qqinterface.servlet.msg.messageelement.MessageElementMaker
|
||||||
import moe.fuqiuluo.qqinterface.servlet.msg.MsgElementMaker
|
import moe.fuqiuluo.qqinterface.servlet.msg.msgelement.MsgElementMaker
|
||||||
import moe.fuqiuluo.shamrock.helper.db.MessageDB
|
import moe.fuqiuluo.shamrock.helper.db.MessageDB
|
||||||
import moe.fuqiuluo.shamrock.helper.db.MessageMapping
|
import moe.fuqiuluo.shamrock.helper.db.MessageMapping
|
||||||
import moe.fuqiuluo.shamrock.remote.structures.SendMsgResult
|
import moe.fuqiuluo.shamrock.remote.structures.SendMsgResult
|
||||||
import moe.fuqiuluo.shamrock.tools.EmptyJsonObject
|
import moe.fuqiuluo.shamrock.tools.EmptyJsonObject
|
||||||
import moe.fuqiuluo.shamrock.tools.asJsonObject
|
|
||||||
import moe.fuqiuluo.shamrock.tools.asJsonObjectOrNull
|
import moe.fuqiuluo.shamrock.tools.asJsonObjectOrNull
|
||||||
import moe.fuqiuluo.shamrock.tools.asString
|
import moe.fuqiuluo.shamrock.tools.asString
|
||||||
import moe.fuqiuluo.shamrock.tools.json
|
import moe.fuqiuluo.shamrock.tools.json
|
||||||
@ -52,7 +51,14 @@ internal object MessageHelper {
|
|||||||
return sendMessageWithoutMsgId(chatType, peerId, msg, fromId, callback)
|
return sendMessageWithoutMsgId(chatType, peerId, msg, fromId, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun resendMsg(chatType: Int, peerId: String, fromId: String, msgId: Long, retryCnt: Int, msgHashId: Int): Result<SendMsgResult> {
|
suspend fun resendMsg(
|
||||||
|
chatType: Int,
|
||||||
|
peerId: String,
|
||||||
|
fromId: String,
|
||||||
|
msgId: Long,
|
||||||
|
retryCnt: Int,
|
||||||
|
msgHashId: Int
|
||||||
|
): Result<SendMsgResult> {
|
||||||
val contact = generateContact(chatType, peerId, fromId)
|
val contact = generateContact(chatType, peerId, fromId)
|
||||||
return resendMsg(contact, msgId, retryCnt, msgHashId)
|
return resendMsg(contact, msgId, retryCnt, msgHashId)
|
||||||
}
|
}
|
||||||
@ -61,11 +67,11 @@ internal object MessageHelper {
|
|||||||
if (retryCnt < 0) return Result.failure(SendMsgException("消息发送超时次数过多"))
|
if (retryCnt < 0) return Result.failure(SendMsgException("消息发送超时次数过多"))
|
||||||
val service = QRoute.api(IMsgService::class.java)
|
val service = QRoute.api(IMsgService::class.java)
|
||||||
val result = withTimeoutOrNull(15000) {
|
val result = withTimeoutOrNull(15000) {
|
||||||
if(suspendCancellableCoroutine {
|
if (suspendCancellableCoroutine {
|
||||||
service.resendMsg(contact, msgId) { result, _ ->
|
service.resendMsg(contact, msgId) { result, _ ->
|
||||||
it.resume(result)
|
it.resume(result)
|
||||||
}
|
}
|
||||||
} != 0) {
|
} != 0) {
|
||||||
resendMsg(contact, msgId, retryCnt - 1, msgHashId)
|
resendMsg(contact, msgId, retryCnt - 1, msgHashId)
|
||||||
} else {
|
} else {
|
||||||
Result.success(SendMsgResult(msgHashId, msgId, System.currentTimeMillis()))
|
Result.success(SendMsgResult(msgHashId, msgId, System.currentTimeMillis()))
|
||||||
@ -244,10 +250,11 @@ internal object MessageHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun generateContact(chatType: Int, id: String, subId: String = ""): Contact {
|
suspend fun generateContact(chatType: Int, id: String, subId: String = ""): Contact {
|
||||||
val peerId = when(chatType) {
|
val peerId = when (chatType) {
|
||||||
MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> {
|
MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> {
|
||||||
ContactHelper.getUidByUinAsync(id.toLong())
|
ContactHelper.getUidByUinAsync(id.toLong())
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> id
|
else -> id
|
||||||
}
|
}
|
||||||
return if (chatType == MsgConstant.KCHATTYPEGUILD) {
|
return if (chatType == MsgConstant.KCHATTYPEGUILD) {
|
||||||
@ -277,7 +284,12 @@ internal object MessageHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun messageArrayToMsgElements(chatType: Int, msgId: Long, targetUin: String, messageList: JsonArray): Pair<Boolean, ArrayList<MsgElement>> {
|
suspend fun messageArrayToMsgElements(
|
||||||
|
chatType: Int,
|
||||||
|
msgId: Long,
|
||||||
|
targetUin: String,
|
||||||
|
messageList: JsonArray
|
||||||
|
): Pair<Boolean, ArrayList<MsgElement>> {
|
||||||
val msgList = arrayListOf<MsgElement>()
|
val msgList = arrayListOf<MsgElement>()
|
||||||
var hasActionMsg = false
|
var hasActionMsg = false
|
||||||
messageList.forEach {
|
messageList.forEach {
|
||||||
@ -306,7 +318,12 @@ internal object MessageHelper {
|
|||||||
return hasActionMsg to msgList
|
return hasActionMsg to msgList
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun messageArrayToMessageElements(chatType: Int, msgId: Long, targetUin: String, messageList: JsonArray): Pair<Boolean, ArrayList<MessageElement>> {
|
suspend fun messageArrayToMessageElements(
|
||||||
|
chatType: Int,
|
||||||
|
msgId: Long,
|
||||||
|
targetUin: String,
|
||||||
|
messageList: JsonArray
|
||||||
|
): Pair<Boolean, ArrayList<MessageElement>> {
|
||||||
val msgList = arrayListOf<MessageElement>()
|
val msgList = arrayListOf<MessageElement>()
|
||||||
var hasActionMsg = false
|
var hasActionMsg = false
|
||||||
messageList.forEach {
|
messageList.forEach {
|
||||||
@ -419,22 +436,6 @@ internal object MessageHelper {
|
|||||||
return arrayList.jsonArray
|
return arrayList.jsonArray
|
||||||
}
|
}
|
||||||
|
|
||||||
fun encodeCQCode(msg: List<Map<String, JsonElement>>): String {
|
|
||||||
return nativeEncodeCQCode(msg.map {
|
|
||||||
val params = hashMapOf<String, String>()
|
|
||||||
it.forEach { (key, value) ->
|
|
||||||
if (key != "type") {
|
|
||||||
value.asJsonObject.forEach { param, element ->
|
|
||||||
params[param] = element.asString
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
params["_type"] = value.asString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
params
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private external fun nativeDecodeCQCode(code: String): List<Map<String, String>>
|
private external fun nativeDecodeCQCode(code: String): List<Map<String, String>>
|
||||||
private external fun nativeEncodeCQCode(segment: List<Map<String, String>>): String
|
external fun nativeEncodeCQCode(segment: List<Map<String, String>>): String
|
||||||
}
|
}
|
@ -1,21 +1,17 @@
|
|||||||
package moe.fuqiuluo.shamrock.remote.action.handlers
|
package moe.fuqiuluo.shamrock.remote.action.handlers
|
||||||
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.msg.convert.MessageConvert
|
|
||||||
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
|
||||||
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
||||||
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
|
import moe.fuqiuluo.shamrock.remote.action.IActionHandler
|
||||||
import moe.fuqiuluo.shamrock.remote.service.data.MessageDetail
|
import moe.fuqiuluo.shamrock.remote.service.data.MessageDetail
|
||||||
import moe.fuqiuluo.shamrock.remote.service.data.MessageSender
|
|
||||||
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
|
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
|
||||||
import moe.fuqiuluo.symbols.OneBotHandler
|
import moe.fuqiuluo.symbols.OneBotHandler
|
||||||
|
|
||||||
@OneBotHandler("get_forward_msg")
|
@OneBotHandler("get_forward_msg")
|
||||||
internal object GetForwardMsg: IActionHandler() {
|
internal object GetForwardMsg : IActionHandler() {
|
||||||
override suspend fun internalHandle(session: ActionSession): String {
|
override suspend fun internalHandle(session: ActionSession): String {
|
||||||
val id = session.getString("id")
|
val id = session.getString("id")
|
||||||
return invoke(id, session.echo)
|
return invoke(id, session.echo)
|
||||||
@ -25,32 +21,8 @@ internal object GetForwardMsg: IActionHandler() {
|
|||||||
resId: String,
|
resId: String,
|
||||||
echo: JsonElement = EmptyJsonString
|
echo: JsonElement = EmptyJsonString
|
||||||
): String {
|
): String {
|
||||||
val result = MsgSvc.getMultiMsg(resId)
|
val result = MsgSvc.getMultiMsg(resId).getOrElse { return logic(it.toString(), echo) }
|
||||||
if (result.isFailure) {
|
return ok(data = GetForwardMsgResult(result), echo = echo)
|
||||||
return logic(result.exceptionOrNull().toString(), echo)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ok(data = GetForwardMsgResult(result.getOrThrow().map { msg ->
|
|
||||||
val msgHash = MessageHelper.generateMsgIdHash(msg.chatType, msg.msgId)
|
|
||||||
MessageDetail(
|
|
||||||
time = msg.msgTime.toInt(),
|
|
||||||
msgType = MessageHelper.obtainDetailTypeByMsgType(msg.chatType),
|
|
||||||
msgId = msgHash,
|
|
||||||
realId = msg.msgSeq.toInt(),
|
|
||||||
sender = MessageSender(
|
|
||||||
msg.senderUin, msg.sendNickName
|
|
||||||
.ifEmpty { msg.sendMemberName }
|
|
||||||
.ifEmpty { msg.sendRemarkName }
|
|
||||||
.ifEmpty { msg.peerName }, "unknown", 0, msg.senderUid, msg.senderUid
|
|
||||||
),
|
|
||||||
message = MessageConvert.convertMessageRecordToMsgSegment(msg).map {
|
|
||||||
it.toJson()
|
|
||||||
},
|
|
||||||
peerId = msg.peerUin,
|
|
||||||
groupId = if (msg.chatType == MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0,
|
|
||||||
targetId = if (msg.chatType != MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0
|
|
||||||
)
|
|
||||||
}), echo = echo)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -6,7 +6,8 @@ import kotlinx.serialization.SerialName
|
|||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.msg.convert.MessageConvert
|
import moe.fuqiuluo.qqinterface.servlet.msg.msgelement.toSegments
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.msg.toListMap
|
||||||
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
||||||
import moe.fuqiuluo.shamrock.helper.db.MessageDB
|
import moe.fuqiuluo.shamrock.helper.db.MessageDB
|
||||||
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
import moe.fuqiuluo.shamrock.remote.action.ActionSession
|
||||||
@ -21,7 +22,7 @@ import kotlin.coroutines.resume
|
|||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
@OneBotHandler("get_history_msg")
|
@OneBotHandler("get_history_msg")
|
||||||
internal object GetHistoryMsg: IActionHandler() {
|
internal object GetHistoryMsg : IActionHandler() {
|
||||||
override suspend fun internalHandle(session: ActionSession): String {
|
override suspend fun internalHandle(session: ActionSession): String {
|
||||||
val msgType = session.getString("message_type")
|
val msgType = session.getString("message_type")
|
||||||
val peerId = session.getString(if (msgType == "group") "group_id" else "user_id")
|
val peerId = session.getString(if (msgType == "group") "group_id" else "user_id")
|
||||||
@ -64,10 +65,10 @@ internal object GetHistoryMsg: IActionHandler() {
|
|||||||
qqMsgId = msg.msgId,
|
qqMsgId = msg.msgId,
|
||||||
chatType = msg.chatType,
|
chatType = msg.chatType,
|
||||||
subChatType = msg.chatType,
|
subChatType = msg.chatType,
|
||||||
peerId = peerId,
|
peerId = msg.peerUin.toString(),
|
||||||
msgSeq = msg.msgSeq.toInt(),
|
msgSeq = msg.msgSeq.toInt(),
|
||||||
time = msg.msgTime,
|
time = msg.msgTime,
|
||||||
subPeerId = msg.channelId ?: peerId
|
subPeerId = msg.channelId ?: msg.peerUin.toString()
|
||||||
)
|
)
|
||||||
MessageDetail(
|
MessageDetail(
|
||||||
time = msg.msgTime.toInt(),
|
time = msg.msgTime.toInt(),
|
||||||
@ -77,9 +78,11 @@ internal object GetHistoryMsg: IActionHandler() {
|
|||||||
sender = MessageSender(
|
sender = MessageSender(
|
||||||
msg.senderUin, msg.sendNickName, "unknown", 0, msg.senderUid, msg.senderUid
|
msg.senderUin, msg.sendNickName, "unknown", 0, msg.senderUid, msg.senderUid
|
||||||
),
|
),
|
||||||
message = MessageConvert.convertMessageRecordToMsgSegment(msg).map {
|
message = msg.elements.toSegments(
|
||||||
it.toJson()
|
msg.chatType,
|
||||||
},
|
if (msg.chatType == MsgConstant.KCHATTYPEGUILD) msg.guildId else msg.peerUin.toString(),
|
||||||
|
msg.channelId ?: msg.peerUin.toString()
|
||||||
|
).toListMap(),
|
||||||
peerId = msg.peerUin,
|
peerId = msg.peerUin,
|
||||||
groupId = if (msg.chatType == MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0,
|
groupId = if (msg.chatType == MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0,
|
||||||
targetId = if (msg.chatType != MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0
|
targetId = if (msg.chatType != MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0
|
||||||
@ -101,13 +104,15 @@ internal object GetHistoryMsg: IActionHandler() {
|
|||||||
.ifEmpty { msg.sendRemarkName }
|
.ifEmpty { msg.sendRemarkName }
|
||||||
.ifEmpty { msg.peerName }, "unknown", 0, msg.senderUid, msg.senderUid
|
.ifEmpty { msg.peerName }, "unknown", 0, msg.senderUid, msg.senderUid
|
||||||
),
|
),
|
||||||
message = MessageConvert.convertMessageRecordToMsgSegment(msg).map {
|
message = msg.elements.toSegments(
|
||||||
it.toJson()
|
chatType,
|
||||||
},
|
if (chatType == MsgConstant.KCHATTYPEGUILD) msg.guildId else msg.peerUin.toString(),
|
||||||
|
msg.channelId ?: peerId).toListMap(),
|
||||||
peerId = msg.peerUin,
|
peerId = msg.peerUin,
|
||||||
groupId = if (msg.chatType == MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0,
|
groupId = if (msg.chatType == MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0,
|
||||||
targetId = if (msg.chatType != MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0
|
targetId = if (msg.chatType != MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0
|
||||||
))
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,8 @@ import moe.fuqiuluo.shamrock.remote.action.IActionHandler
|
|||||||
import moe.fuqiuluo.shamrock.remote.service.data.MessageDetail
|
import moe.fuqiuluo.shamrock.remote.service.data.MessageDetail
|
||||||
import moe.fuqiuluo.shamrock.remote.service.data.MessageSender
|
import moe.fuqiuluo.shamrock.remote.service.data.MessageSender
|
||||||
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.msg.convert.MessageConvert
|
import moe.fuqiuluo.qqinterface.servlet.msg.msgelement.toSegments
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.msg.toListMap
|
||||||
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
|
import moe.fuqiuluo.shamrock.tools.EmptyJsonString
|
||||||
import moe.fuqiuluo.symbols.OneBotHandler
|
import moe.fuqiuluo.symbols.OneBotHandler
|
||||||
|
|
||||||
@ -39,9 +40,11 @@ internal object GetMsg: IActionHandler() {
|
|||||||
msg.senderUid,
|
msg.senderUid,
|
||||||
msg.senderUid
|
msg.senderUid
|
||||||
),
|
),
|
||||||
message = MessageConvert.convertMessageRecordToMsgSegment(msg).map {
|
message = msg.elements.toSegments(
|
||||||
it.toJson()
|
msg.chatType,
|
||||||
},
|
if (msg.chatType == MsgConstant.KCHATTYPEGUILD) msg.guildId else msg.peerUin.toString(),
|
||||||
|
msg.channelId ?: msg.peerUin.toString()
|
||||||
|
).toListMap(),
|
||||||
peerId = msg.peerUin,
|
peerId = msg.peerUin,
|
||||||
groupId = if (msg.chatType == MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0,
|
groupId = if (msg.chatType == MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0,
|
||||||
targetId = if (msg.chatType != MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0
|
targetId = if (msg.chatType != MsgConstant.KCHATTYPEGROUP) msg.peerUin else 0
|
||||||
|
@ -4,7 +4,7 @@ import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
|||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.*
|
||||||
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.msg.convert.toSegments
|
import moe.fuqiuluo.qqinterface.servlet.msg.msgelement.toSegments
|
||||||
import moe.fuqiuluo.shamrock.helper.Level
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
||||||
@ -128,8 +128,12 @@ internal object SendForwardMessage : IActionHandler() {
|
|||||||
record.chatType,
|
record.chatType,
|
||||||
record.msgId,
|
record.msgId,
|
||||||
record.peerUin.toString(),
|
record.peerUin.toString(),
|
||||||
record.elements.toSegments(record.chatType, record.peerUin.toString(), "0").also {
|
record.elements.toSegments(
|
||||||
desc[++i] = record.peerName + ": "
|
record.chatType,
|
||||||
|
record.peerUin.toString(),
|
||||||
|
"0"
|
||||||
|
).also {
|
||||||
|
desc[++i] = record.sendMemberName.ifEmpty { record.sendNickName } + ": "
|
||||||
}.map {
|
}.map {
|
||||||
when (it.type) {
|
when (it.type) {
|
||||||
"text" -> desc[i] += it.data["text"] as String
|
"text" -> desc[i] += it.data["text"] as String
|
||||||
@ -142,7 +146,6 @@ internal object SendForwardMessage : IActionHandler() {
|
|||||||
|
|
||||||
"node" -> desc[i] += "[合并转发消息]"
|
"node" -> desc[i] += "[合并转发消息]"
|
||||||
}
|
}
|
||||||
|
|
||||||
it.toJson()
|
it.toJson()
|
||||||
}.json
|
}.json
|
||||||
).also {
|
).also {
|
||||||
@ -157,10 +160,10 @@ internal object SendForwardMessage : IActionHandler() {
|
|||||||
peerUid = data["uid"]?.asString ?: TicketSvc.getUid()
|
peerUid = data["uid"]?.asString ?: TicketSvc.getUid()
|
||||||
),
|
),
|
||||||
content = MessageContent(
|
content = MessageContent(
|
||||||
msgType = 529,
|
msgType = 166,
|
||||||
msgViaRandom = 4,
|
msgViaRandom = 4,
|
||||||
msgSeq = data["seq"]?.asLong ?: 0,
|
msgSeq = data["seq"]?.asLong ?: Random.nextLong(),
|
||||||
msgTime = System.currentTimeMillis() / 1000,
|
msgTime = data["time"]?.asLong ?: (System.currentTimeMillis() / 1000),
|
||||||
u2 = 1,
|
u2 = 1,
|
||||||
u6 = 0,
|
u6 = 0,
|
||||||
u7 = 0,
|
u7 = 0,
|
||||||
@ -221,7 +224,8 @@ internal object SendForwardMessage : IActionHandler() {
|
|||||||
chatType, peerId,
|
chatType, peerId,
|
||||||
listOf(
|
listOf(
|
||||||
hashMapOf(
|
hashMapOf(
|
||||||
"type" to "json", "data" to hashMapOf(
|
"type" to "json",
|
||||||
|
"data" to hashMapOf(
|
||||||
"data" to hashMapOf(
|
"data" to hashMapOf(
|
||||||
"app" to "com.tencent.multimsg",
|
"app" to "com.tencent.multimsg",
|
||||||
"config" to hashMapOf(
|
"config" to hashMapOf(
|
||||||
@ -235,7 +239,7 @@ internal object SendForwardMessage : IActionHandler() {
|
|||||||
"extra" to hashMapOf(
|
"extra" to hashMapOf(
|
||||||
"filename" to uniseq,
|
"filename" to uniseq,
|
||||||
"tsum" to 2
|
"tsum" to 2
|
||||||
).json.toString() + "\n",
|
).json.toString(),
|
||||||
"meta" to hashMapOf(
|
"meta" to hashMapOf(
|
||||||
"detail" to hashMapOf(
|
"detail" to hashMapOf(
|
||||||
"news" to desc.slice(0..if (i < 3) i else 3)
|
"news" to desc.slice(0..if (i < 3) i else 3)
|
||||||
|
@ -1,27 +1,9 @@
|
|||||||
package moe.fuqiuluo.shamrock.remote.api
|
package moe.fuqiuluo.shamrock.remote.api
|
||||||
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.Contact
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.IMsgOperateCallback
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
|
|
||||||
import com.tencent.qqnt.kernel.nativeinterface.TextElement
|
|
||||||
import io.ktor.server.application.call
|
|
||||||
import io.ktor.server.response.respondText
|
|
||||||
import io.ktor.server.routing.Routing
|
import io.ktor.server.routing.Routing
|
||||||
import io.ktor.server.routing.get
|
|
||||||
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
|
||||||
import moe.fuqiuluo.qqinterface.servlet.msg.convert.toCQCode
|
|
||||||
import moe.fuqiuluo.shamrock.helper.Level
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||||
import moe.fuqiuluo.shamrock.helper.MessageHelper
|
|
||||||
import moe.fuqiuluo.shamrock.tools.ShamrockVersion
|
import moe.fuqiuluo.shamrock.tools.ShamrockVersion
|
||||||
import moe.fuqiuluo.shamrock.tools.fetch
|
|
||||||
import moe.fuqiuluo.shamrock.tools.fetchOrThrow
|
|
||||||
import moe.fuqiuluo.shamrock.xposed.helper.NTServiceFetcher
|
|
||||||
import java.util.ArrayList
|
|
||||||
import kotlin.coroutines.resume
|
|
||||||
import kotlin.coroutines.suspendCoroutine
|
|
||||||
|
|
||||||
fun Routing.testAction() {
|
fun Routing.testAction() {
|
||||||
if(ShamrockVersion.contains("dev")) {
|
if(ShamrockVersion.contains("dev")) {
|
||||||
|
@ -6,14 +6,14 @@ import com.tencent.qqnt.kernel.nativeinterface.MsgElement
|
|||||||
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
|
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
|
||||||
import kotlinx.coroutines.flow.FlowCollector
|
import kotlinx.coroutines.flow.FlowCollector
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
import moe.fuqiuluo.qqinterface.servlet.BaseSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.CardSvc
|
import moe.fuqiuluo.qqinterface.servlet.CardSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.GroupSvc
|
import moe.fuqiuluo.qqinterface.servlet.GroupSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.msg.convert.toSegments
|
import moe.fuqiuluo.qqinterface.servlet.msg.msgelement.toSegments
|
||||||
|
import moe.fuqiuluo.qqinterface.servlet.msg.toJson
|
||||||
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
||||||
import moe.fuqiuluo.shamrock.remote.service.data.push.GroupFileMsg
|
import moe.fuqiuluo.shamrock.remote.service.data.push.GroupFileMsg
|
||||||
import moe.fuqiuluo.shamrock.remote.service.data.push.MemberRole
|
import moe.fuqiuluo.shamrock.remote.service.data.push.MemberRole
|
||||||
@ -80,9 +80,7 @@ internal object GlobalEventTransmitter: BaseSvc() {
|
|||||||
peerId = uin,
|
peerId = uin,
|
||||||
userId = record.senderUin,
|
userId = record.senderUin,
|
||||||
message = if(ShamrockConfig.useCQ()) rawMsg.json
|
message = if(ShamrockConfig.useCQ()) rawMsg.json
|
||||||
else elements.toSegments(record.chatType, record.peerUin.toString(), "0").map {
|
else elements.toSegments(record.chatType, record.peerUin.toString(), "0").toJson(),
|
||||||
it.toJson()
|
|
||||||
}.json,
|
|
||||||
rawMessage = rawMsg,
|
rawMessage = rawMsg,
|
||||||
font = 0,
|
font = 0,
|
||||||
sender = Sender(
|
sender = Sender(
|
||||||
@ -137,9 +135,7 @@ internal object GlobalEventTransmitter: BaseSvc() {
|
|||||||
peerId = botUin,
|
peerId = botUin,
|
||||||
userId = record.senderUin,
|
userId = record.senderUin,
|
||||||
message = if(ShamrockConfig.useCQ()) rawMsg.json
|
message = if(ShamrockConfig.useCQ()) rawMsg.json
|
||||||
else elements.toSegments(record.chatType, record.peerUin.toString(), "0").map {
|
else elements.toSegments(record.chatType, record.peerUin.toString(), "0").toJson(),
|
||||||
it.toJson()
|
|
||||||
}.json,
|
|
||||||
rawMessage = rawMsg,
|
rawMessage = rawMsg,
|
||||||
font = 0,
|
font = 0,
|
||||||
sender = Sender(
|
sender = Sender(
|
||||||
@ -187,15 +183,13 @@ internal object GlobalEventTransmitter: BaseSvc() {
|
|||||||
messageId = msgHash,
|
messageId = msgHash,
|
||||||
targetId = record.peerUin,
|
targetId = record.peerUin,
|
||||||
peerId = botUin,
|
peerId = botUin,
|
||||||
userId = record.senderUid.toLong(),
|
userId = record.senderUin,
|
||||||
message = if(ShamrockConfig.useCQ()) rawMsg.json
|
message = if(ShamrockConfig.useCQ()) rawMsg.json
|
||||||
else elements.toSegments(record.chatType, record.guildId, record.channelId).map {
|
else elements.toSegments(record.chatType, record.guildId, record.channelId).toJson(),
|
||||||
it.toJson()
|
|
||||||
}.json,
|
|
||||||
rawMessage = rawMsg,
|
rawMessage = rawMsg,
|
||||||
font = 0,
|
font = 0,
|
||||||
sender = Sender(
|
sender = Sender(
|
||||||
userId = record.senderUid.toLong(),
|
userId = record.senderUin,
|
||||||
nickname = nickName,
|
nickname = nickName,
|
||||||
card = record.sendMemberName,
|
card = record.sendMemberName,
|
||||||
role = MemberRole.Member, // TODO(GUILD ROLE)
|
role = MemberRole.Member, // TODO(GUILD ROLE)
|
||||||
|
@ -9,7 +9,7 @@ import kotlinx.coroutines.GlobalScope
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
import moe.fuqiuluo.qqinterface.servlet.MsgSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
import moe.fuqiuluo.qqinterface.servlet.TicketSvc
|
||||||
import moe.fuqiuluo.qqinterface.servlet.msg.convert.toCQCode
|
import moe.fuqiuluo.qqinterface.servlet.msg.msgelement.toCQCode
|
||||||
import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc
|
import moe.fuqiuluo.qqinterface.servlet.transfile.RichProtoSvc
|
||||||
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
import moe.fuqiuluo.shamrock.remote.service.config.ShamrockConfig
|
||||||
import moe.fuqiuluo.shamrock.helper.Level
|
import moe.fuqiuluo.shamrock.helper.Level
|
||||||
|
Loading…
x
Reference in New Issue
Block a user