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

This commit is contained in:
Simplxs 2024-02-20 23:23:53 +08:00
parent c70f3eabfe
commit aa7b241dba
No known key found for this signature in database
GPG Key ID: E23537FF14DD6507
20 changed files with 1433 additions and 908 deletions

View File

@ -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,
) )

View File

@ -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

View File

@ -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
) )

View File

@ -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(
LongMsgAction(
command = "MultiMsg", command = "MultiMsg",
data = LongMsgContent( data = LongMsgContent(
messages 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(
"trpc.group.long_msg_interface.MsgService.SsoRecvLongMsg",
true,
ProtoBuf.encodeToByteArray(req)
) ?: return Result.failure(Exception("unable to get multi message"))
val rsp = ProtoBuf.decodeFromByteArray<LongMsgRsp>(buffer.slice(4))
val msg = DeflateTools.ungzip(
rsp.recvResult?.payload ?: return Result.failure(Exception("unable to get multi message"))
)
val payload = ProtoBuf.decodeFromByteArray<LongMsgPayload>(msg)
payload.action?.forEach {
if (it.command == "MultiMsg") {
return Result.success(it.data?.body?.map { msg ->
val chatType =
if (msg.content!!.msgType == 1) MsgConstant.KCHATTYPEC2C else MsgConstant.KCHATTYPEGROUP
MessageDetail(
time = msg.content?.msgTime?.toInt() ?: 0,
msgType = MessageHelper.obtainDetailTypeByMsgType(chatType),
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
)
} }
val msgList = withTimeoutOrNull(5000L) { ?: return Result.failure(Exception("Msg is empty")))
suspendCancellableCoroutine<ArrayList<MsgRecord>> {
val job = GlobalScope.launch {
var hasResult = false
while (!hasResult) {
msgService.getMultiMsg(contact, msgId, msgId) { code, why, msgList ->
if (code == 0) {
it.resume(msgList)
hasResult = true
} else {
LogCenter.log("获取合并转发消息失败: $code($why): $msgId", Level.ERROR)
} }
} }
delay(200) return Result.failure(Exception("Can't find msg"))
}
}
it.invokeOnCancellation {
job.cancel()
}
}
} ?: return Result.failure(Exception("获取合并转发消息失败"))
//msgService.deleteMsg(contact, arrayListOf(msgId), null)
return Result.success(msgList)
} }
class MessageCallback( class MessageCallback(

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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")
}
}

View File

@ -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()
// )
// )
// }
}

View File

@ -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]
} }

View File

@ -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()
)
)
}
}

View File

@ -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]
} }

View File

@ -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,7 +67,7 @@ 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)
} }
@ -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
} }

View File

@ -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

View File

@ -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
)) )
)
} }
} }

View File

@ -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

View File

@ -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)

View File

@ -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")) {

View File

@ -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)

View File

@ -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