diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/atImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/atImpl.kt new file mode 100644 index 000000000..72b9bbb07 --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/atImpl.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.qqandroid.message + +import kotlinx.io.core.buildPacket +import kotlinx.io.core.readBytes +import net.mamoe.mirai.message.data.At +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody + + +internal fun At.toJceData(): ImMsgBody.Text { + val text = this.toString() + return ImMsgBody.Text( + str = text, + attr6Buf = buildPacket { + // MessageForText$AtTroopMemberInfo + writeShort(1) // const + writeShort(0) // startPos + writeShort(text.length.toShort()) // textLen + writeByte(0) // flag, may=1 + writeInt(target.toInt()) // uin + writeShort(0) // const + }.readBytes() + ) +} + + +internal val atAllData = ImMsgBody.Elem( + text = ImMsgBody.Text( + str = "@全体成员", + attr6Buf = buildPacket { + // MessageForText$AtTroopMemberInfo + writeShort(1) // const + writeShort(0) // startPos + writeShort("@全体成员".length.toShort()) // textLen + writeByte(1) // flag, may=1 + writeInt(0) // uin + writeShort(0) // const + }.readBytes() + ) +) \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/convension.kt similarity index 64% rename from mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt rename to mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/convension.kt index c509371b7..70b772389 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/convension.kt @@ -11,108 +11,25 @@ package net.mamoe.mirai.qqandroid.message -import kotlinx.io.core.* +import kotlinx.io.core.discardExact +import kotlinx.io.core.readUInt +import kotlinx.io.core.toByteArray import net.mamoe.mirai.LowLevelAPI import net.mamoe.mirai.contact.Member import net.mamoe.mirai.message.data.* +import net.mamoe.mirai.qqandroid.io.serialization.loadAs +import net.mamoe.mirai.qqandroid.io.serialization.toByteArray +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.HummerCommelem import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.io.encodeToString import net.mamoe.mirai.utils.io.hexToBytes import net.mamoe.mirai.utils.io.read -import net.mamoe.mirai.utils.io.toByteArray -internal fun At.toJceData(): ImMsgBody.Text { - val text = this.toString() - return ImMsgBody.Text( - str = text, - attr6Buf = buildPacket { - // MessageForText$AtTroopMemberInfo - writeShort(1) // const - writeShort(0) // startPos - writeShort(text.length.toShort()) // textLen - writeByte(0) // flag, may=1 - writeInt(target.toInt()) // uin - writeShort(0) // const - }.readBytes() - ) -} - -internal fun OfflineFriendImage.toJceData(): ImMsgBody.NotOnlineImage { - return ImMsgBody.NotOnlineImage( - filePath = this.filepath, - resId = this.resourceId, - oldPicMd5 = false, - picMd5 = this.md5, - fileLen = this.fileLength, - picHeight = this.height, - picWidth = this.width, - bizType = this.bizType, - imgType = this.imageType, - downloadPath = this.downloadPath, - original = this.original, - fileId = this.fileId, - pbReserve = byteArrayOf(0x78, 0x02) - ) -} - -internal val FACE_BUF = "00 01 00 04 52 CC F5 D0".hexToBytes() - -internal fun Face.toJceData(): ImMsgBody.Face { - return ImMsgBody.Face( - index = this.id, - old = (0x1445 - 4 + this.id).toShort().toByteArray(), - buf = FACE_BUF - ) -} - -internal fun OfflineGroupImage.toJceData(): ImMsgBody.CustomFace { - return ImMsgBody.CustomFace( - filePath = this.filepath, - fileId = this.fileId, - serverIp = this.serverIp, - serverPort = this.serverPort, - fileType = this.fileType, - signature = this.signature, - useful = this.useful, - md5 = this.md5, - bizType = this.bizType, - imageType = this.imageType, - width = this.width, - height = this.height, - source = this.source, - size = this.size, - origin = this.original, - pbReserve = this.pbReserve, - flag = ByteArray(4), - //_400Height = 235, - //_400Url = "/gchatpic_new/1040400290/1041235568-2195821338-01E9451B70EDEAE3B37C101F1EEBF5B5/400?term=2", - //_400Width = 351, - oldData = oldData - ) -} - -private val oldData: ByteArray = - "15 36 20 39 32 6B 41 31 00 38 37 32 66 30 36 36 30 33 61 65 31 30 33 62 37 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 30 31 45 39 34 35 31 42 2D 37 30 45 44 2D 45 41 45 33 2D 42 33 37 43 2D 31 30 31 46 31 45 45 42 46 35 42 35 7D 2E 70 6E 67 41".hexToBytes() - - -private val atAllData = ImMsgBody.Elem( - text = ImMsgBody.Text( - str = "@全体成员", - attr6Buf = buildPacket { - // MessageForText$AtTroopMemberInfo - writeShort(1) // const - writeShort(0) // startPos - writeShort("@全体成员".length.toShort()) // textLen - writeByte(1) // flag, may=1 - writeInt(0) // uin - writeShort(0) // const - }.readBytes() - ) -) private val UNSUPPORTED_MERGED_MESSAGE_PLAIN = PlainText("你的QQ暂不支持查看[转发多条消息],请期待后续版本。") +private val UNSUPPORTED_POKE_MESSAGE_PLAIN = PlainText("[戳一戳]请使用最新版手机QQ体验新功能。") @OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class) internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: Boolean): MutableList { @@ -175,6 +92,21 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B elements.add(ImMsgBody.Elem(text = it.toJceData())) elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = " "))) } + is PokeMessage -> { + elements.add( + ImMsgBody.Elem( + commonElem = ImMsgBody.CommonElem( + serviceType = 2, + businessType = it.type, + pbElem = HummerCommelem.MsgElemInfoServtype2( + pokeType = it.type, + vaspokeId = it.id + ).toByteArray(HummerCommelem.MsgElemInfoServtype2.serializer()) + ) + ) + ) + transformOneMessage(UNSUPPORTED_POKE_MESSAGE_PLAIN) + } is OfflineGroupImage -> elements.add(ImMsgBody.Elem(customFace = it.toJceData())) is OnlineGroupImageImpl -> elements.add(ImMsgBody.Elem(customFace = it.delegate)) is OnlineFriendImageImpl -> elements.add(ImMsgBody.Elem(notOnlineImage = it.delegate)) @@ -231,65 +163,6 @@ private val PB_RESERVE_FOR_RICH_MESSAGE = "08 09 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 02 08 03 90 04 80 80 80 10 B8 04 00 C0 04 00".hexToBytes() private val PB_RESERVE_FOR_ELSE = "78 00 F8 01 00 C8 02 00".hexToBytes() -internal class OnlineGroupImageImpl( - internal val delegate: ImMsgBody.CustomFace -) : OnlineGroupImage() { - override val filepath: String = delegate.filePath - override val fileId: Int get() = delegate.fileId - override val serverIp: Int get() = delegate.serverIp - override val serverPort: Int get() = delegate.serverPort - override val fileType: Int get() = delegate.fileType - override val signature: ByteArray get() = delegate.signature - override val useful: Int get() = delegate.useful - override val md5: ByteArray get() = delegate.md5 - override val bizType: Int get() = delegate.bizType - override val imageType: Int get() = delegate.imageType - override val width: Int get() = delegate.width - override val height: Int get() = delegate.height - override val source: Int get() = delegate.source - override val size: Int get() = delegate.size - override val original: Int get() = delegate.origin - override val pbReserve: ByteArray get() = delegate.pbReserve - override val imageId: String = ExternalImage.generateImageId(delegate.md5, imageType) - override val originUrl: String - get() = "http://gchat.qpic.cn" + delegate.origUrl - - override fun equals(other: Any?): Boolean { - return other is OnlineGroupImageImpl && other.filepath == this.filepath && other.md5.contentEquals(this.md5) - } - - override fun hashCode(): Int { - return imageId.hashCode() + 31 * md5.hashCode() - } -} - -internal class OnlineFriendImageImpl( - internal val delegate: ImMsgBody.NotOnlineImage -) : OnlineFriendImage() { - override val resourceId: String get() = delegate.resId - override val md5: ByteArray get() = delegate.picMd5 - override val filepath: String get() = delegate.filePath - override val fileLength: Int get() = delegate.fileLen - override val height: Int get() = delegate.picHeight - override val width: Int get() = delegate.picWidth - override val bizType: Int get() = delegate.bizType - override val imageType: Int get() = delegate.imgType - override val downloadPath: String get() = delegate.downloadPath - override val fileId: Int get() = delegate.fileId - override val original: Int get() = delegate.original - override val originUrl: String - get() = "http://c2cpicdw.qpic.cn" + this.delegate.origUrl - - override fun equals(other: Any?): Boolean { - return other is OnlineFriendImageImpl && other.resourceId == this.resourceId && other.md5 - .contentEquals(this.md5) - } - - override fun hashCode(): Int { - return imageId.hashCode() + 31 * md5.hashCode() - } -} - @OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class) internal fun MsgComm.Msg.toMessageChain(): MessageChain { val elements = this.msgBody.richText.elems @@ -337,6 +210,7 @@ internal inline fun Iterable<*>.firstIsInstance(): R { throw NoSuchElementException("Collection contains no element matching the predicate.") } +@OptIn(MiraiInternalAPI::class, LowLevelAPI::class) internal fun List.joinToMessageChain(message: MessageChainBuilder) { this.forEach { when { @@ -383,6 +257,22 @@ internal fun List.joinToMessageChain(message: MessageChainBuilde } } } + it.elemFlags2 != null + || it.extraInfo != null + || it.generalFlags != null -> { + + } + it.commonElem != null -> { + when (it.commonElem.serviceType) { + 2 -> { + val proto = it.commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype2.serializer()) + message.add(PokeMessage(proto.pokeType, proto.vaspokeId)) + } + } + } + else -> { + println(it._miraiContentToString()) + } } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/faceImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/faceImpl.kt new file mode 100644 index 000000000..6cf4280b1 --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/faceImpl.kt @@ -0,0 +1,16 @@ +package net.mamoe.mirai.qqandroid.message + +import net.mamoe.mirai.message.data.Face +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody +import net.mamoe.mirai.utils.io.hexToBytes +import net.mamoe.mirai.utils.io.toByteArray + +internal val FACE_BUF = "00 01 00 04 52 CC F5 D0".hexToBytes() + +internal fun Face.toJceData(): ImMsgBody.Face { + return ImMsgBody.Face( + index = this.id, + old = (0x1445 - 4 + this.id).toShort().toByteArray(), + buf = FACE_BUF + ) +} diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/imagesImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/imagesImpl.kt new file mode 100644 index 000000000..e2d2e4efe --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/imagesImpl.kt @@ -0,0 +1,126 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.qqandroid.message + +import net.mamoe.mirai.message.data.OfflineFriendImage +import net.mamoe.mirai.message.data.OfflineGroupImage +import net.mamoe.mirai.message.data.OnlineFriendImage +import net.mamoe.mirai.message.data.OnlineGroupImage +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody +import net.mamoe.mirai.utils.ExternalImage +import net.mamoe.mirai.utils.io.hexToBytes + + +internal class OnlineGroupImageImpl( + internal val delegate: ImMsgBody.CustomFace +) : OnlineGroupImage() { + override val filepath: String = delegate.filePath + override val fileId: Int get() = delegate.fileId + override val serverIp: Int get() = delegate.serverIp + override val serverPort: Int get() = delegate.serverPort + override val fileType: Int get() = delegate.fileType + override val signature: ByteArray get() = delegate.signature + override val useful: Int get() = delegate.useful + override val md5: ByteArray get() = delegate.md5 + override val bizType: Int get() = delegate.bizType + override val imageType: Int get() = delegate.imageType + override val width: Int get() = delegate.width + override val height: Int get() = delegate.height + override val source: Int get() = delegate.source + override val size: Int get() = delegate.size + override val original: Int get() = delegate.origin + override val pbReserve: ByteArray get() = delegate.pbReserve + override val imageId: String = ExternalImage.generateImageId(delegate.md5, imageType) + override val originUrl: String + get() = "http://gchat.qpic.cn" + delegate.origUrl + + override fun equals(other: Any?): Boolean { + return other is OnlineGroupImageImpl && other.filepath == this.filepath && other.md5.contentEquals(this.md5) + } + + override fun hashCode(): Int { + return imageId.hashCode() + 31 * md5.hashCode() + } +} + +internal class OnlineFriendImageImpl( + internal val delegate: ImMsgBody.NotOnlineImage +) : OnlineFriendImage() { + override val resourceId: String get() = delegate.resId + override val md5: ByteArray get() = delegate.picMd5 + override val filepath: String get() = delegate.filePath + override val fileLength: Int get() = delegate.fileLen + override val height: Int get() = delegate.picHeight + override val width: Int get() = delegate.picWidth + override val bizType: Int get() = delegate.bizType + override val imageType: Int get() = delegate.imgType + override val downloadPath: String get() = delegate.downloadPath + override val fileId: Int get() = delegate.fileId + override val original: Int get() = delegate.original + override val originUrl: String + get() = "http://c2cpicdw.qpic.cn" + this.delegate.origUrl + + override fun equals(other: Any?): Boolean { + return other is OnlineFriendImageImpl && other.resourceId == this.resourceId && other.md5 + .contentEquals(this.md5) + } + + override fun hashCode(): Int { + return imageId.hashCode() + 31 * md5.hashCode() + } +} + +internal fun OfflineGroupImage.toJceData(): ImMsgBody.CustomFace { + return ImMsgBody.CustomFace( + filePath = this.filepath, + fileId = this.fileId, + serverIp = this.serverIp, + serverPort = this.serverPort, + fileType = this.fileType, + signature = this.signature, + useful = this.useful, + md5 = this.md5, + bizType = this.bizType, + imageType = this.imageType, + width = this.width, + height = this.height, + source = this.source, + size = this.size, + origin = this.original, + pbReserve = this.pbReserve, + flag = ByteArray(4), + //_400Height = 235, + //_400Url = "/gchatpic_new/1040400290/1041235568-2195821338-01E9451B70EDEAE3B37C101F1EEBF5B5/400?term=2", + //_400Width = 351, + oldData = oldData + ) +} + +private val oldData: ByteArray = + "15 36 20 39 32 6B 41 31 00 38 37 32 66 30 36 36 30 33 61 65 31 30 33 62 37 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 30 31 45 39 34 35 31 42 2D 37 30 45 44 2D 45 41 45 33 2D 42 33 37 43 2D 31 30 31 46 31 45 45 42 46 35 42 35 7D 2E 70 6E 67 41".hexToBytes() + + +internal fun OfflineFriendImage.toJceData(): ImMsgBody.NotOnlineImage { + return ImMsgBody.NotOnlineImage( + filePath = this.filepath, + resId = this.resourceId, + oldPicMd5 = false, + picMd5 = this.md5, + fileLen = this.fileLength, + picHeight = this.height, + picWidth = this.width, + bizType = this.bizType, + imgType = this.imageType, + downloadPath = this.downloadPath, + original = this.original, + fileId = this.fileId, + pbReserve = byteArrayOf(0x78, 0x02) + ) +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/HummerCommelem.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/HummerCommelem.kt new file mode 100644 index 000000000..b3261f15d --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/HummerCommelem.kt @@ -0,0 +1,272 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.qqandroid.network.protocol.data.proto + +import kotlinx.serialization.Serializable +import kotlinx.serialization.protobuf.ProtoId +import net.mamoe.mirai.qqandroid.io.ProtoBuf +import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY + +internal class HummerCommelem : ProtoBuf { + @Serializable + class MsgElemInfoServtype1( + @ProtoId(1) val rewardId: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val senderUin: Long = 0L, + @ProtoId(3) val picType: Int = 0, + @ProtoId(4) val rewardMoney: Int = 0, + @ProtoId(5) val url: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(6) val content: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(7) val createTimestamp: Int = 0, + @ProtoId(8) val status: Int = 0, + @ProtoId(9) val size: Int = 0, + @ProtoId(10) val videoDuration: Int = 0, + @ProtoId(11) val seq: Long = 0L, + @ProtoId(12) val rewardTypeExt: Int = 0 + ) : ProtoBuf + + @Serializable + class MsgElemInfoServtype11( + @ProtoId(1) val resID: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val resMD5: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val reserveInfo1: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val reserveInfo2: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val doodleDataOffset: Int = 0, + @ProtoId(6) val doodleGifId: Int = 0, + @ProtoId(7) val doodleUrl: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(8) val doodleMd5: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class MsgElemInfoServtype13( + @ProtoId(1) val sysHeadId: Int = 0, + @ProtoId(2) val headFlag: Int = 0 + ) : ProtoBuf + + @Serializable + class MsgElemInfoServtype14( + @ProtoId(1) val id: Int = 0, + @ProtoId(2) val reserveInfo: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class MsgElemInfoServtype15( + @ProtoId(1) val vid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val cover: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val title: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val summary: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val createTime: Long = 0L, + @ProtoId(6) val commentContent: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(7) val author: Long = 0L, + @ProtoId(8) val ctrVersion: Int = 0 + ) : ProtoBuf + + @Serializable + class MsgElemInfoServtype16( + @ProtoId(1) val uid: Long = 0L, + @ProtoId(2) val unionID: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val storyID: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val md5: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val thumbUrl: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(6) val doodleUrl: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(7) val videoWidth: Int = 0, + @ProtoId(8) val videoHeight: Int = 0, + @ProtoId(9) val sourceName: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(10) val sourceActionType: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(11) val sourceActionData: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(12) val ctrVersion: Int = 0 + ) : ProtoBuf + + @Serializable + class MsgElemInfoServtype18( + @ProtoId(1) val currentAmount: Long = 0L, + @ProtoId(2) val totalAmount: Long = 0L, + @ProtoId(3) val listid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val authKey: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val number: Int = 0 + ) : ProtoBuf + + @Serializable + class MsgElemInfoServtype19( + @ProtoId(1) val data: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class MsgElemInfoServtype2( + @ProtoId(1) val pokeType: Int = 0, + @ProtoId(2) val pokeSummary: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val doubleHit: Int = 0, + @ProtoId(4) val vaspokeId: Int = 0, + @ProtoId(5) val vaspokeName: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(6) val vaspokeMinver: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(7) val pokeStrength: Int = 0, + @ProtoId(8) val msgType: Int = 0, + @ProtoId(9) val faceBubbleCount: Int = 0, + @ProtoId(10) val pokeFlag: Int = 0 + ) : ProtoBuf + + @Serializable + class MsgElemInfoServtype20( + @ProtoId(1) val data: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class MsgElemInfoServtype21( + @ProtoId(1) val topicId: Int = 0, + @ProtoId(2) val confessorUin: Long = 0L, + @ProtoId(3) val confessorNick: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val confessorSex: Int = 0, + @ProtoId(5) val sysmsgFlag: Int = 0, + @ProtoId(6) val c2cConfessCtx: HummerCommelem.MsgElemInfoServtype21.C2CConfessContext? = null, + @ProtoId(7) val topic: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(8) val confessTime: Long = 0L, + @ProtoId(9) val groupConfessMsg: HummerCommelem.MsgElemInfoServtype21.GroupConfessMsg? = null, + @ProtoId(10) val groupConfessCtx: HummerCommelem.MsgElemInfoServtype21.GroupConfessContext? = null + ) : ProtoBuf { + @Serializable + class C2CConfessContext( + @ProtoId(1) val confessorUin: Long = 0L, + @ProtoId(2) val confessToUin: Long = 0L, + @ProtoId(3) val sendUin: Long = 0L, + @ProtoId(4) val confessorNick: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val confess: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(6) val bgType: Int = 0, + @ProtoId(7) val topicId: Int = 0, + @ProtoId(8) val confessTime: Long = 0L, + @ProtoId(9) val confessorSex: Int = 0, + @ProtoId(10) val bizType: Int = 0, + @ProtoId(11) val confessNum: Int = 0, + @ProtoId(12) val confessToSex: Int = 0 + ) : ProtoBuf + + @Serializable + class GroupConfessContext( + @ProtoId(1) val confessorUin: Long = 0L, + @ProtoId(2) val confessToUin: Long = 0L, + @ProtoId(3) val sendUin: Long = 0L, + @ProtoId(4) val confessorSex: Int = 0, + @ProtoId(5) val confessToNick: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(6) val topic: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(7) val topicId: Int = 0, + @ProtoId(8) val confessTime: Long = 0L, + @ProtoId(9) val confessToNickType: Int = 0, + @ProtoId(10) val confessorNick: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class GroupConfessItem( + @ProtoId(1) val topicId: Int = 0, + @ProtoId(2) val confessToUin: Long = 0L, + @ProtoId(3) val confessToNick: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val topic: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val confessToNickType: Int = 0 + ) : ProtoBuf + + @Serializable + class GroupConfessMsg( + @ProtoId(1) val confessTime: Long = 0L, + @ProtoId(2) val confessorUin: Long = 0L, + @ProtoId(3) val confessorSex: Int = 0, + @ProtoId(4) val sysmsgFlag: Int = 0, + @ProtoId(5) val confessItems: List? = null, + @ProtoId(6) val totalTopicCount: Int = 0 + ) : ProtoBuf + } + + @Serializable + class MsgElemInfoServtype23( + @ProtoId(1) val faceType: Int = 0, + @ProtoId(2) val faceBubbleCount: Int = 0, + @ProtoId(3) val faceSummary: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val flag: Int = 0, + @ProtoId(5) val others: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class MsgElemInfoServtype24( + @ProtoId(1) val limitChatEnter: HummerCommelem.MsgElemInfoServtype24.LimitChatEnter? = null, + @ProtoId(2) val limitChatExit: HummerCommelem.MsgElemInfoServtype24.LimitChatExit? = null + ) : ProtoBuf { + @Serializable + class LimitChatEnter( + @ProtoId(1) val tipsWording: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val leftChatTime: Int = 0, + @ProtoId(3) val matchTs: Long = 0L, + @ProtoId(4) val matchExpiredTime: Int = 0, + @ProtoId(5) val c2cExpiredTime: Int = 0, + @ProtoId(6) val readyTs: Long = 0L, + @ProtoId(7) val matchNick: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class LimitChatExit( + @ProtoId(1) val exitMethod: Int = 0, + @ProtoId(2) val matchTs: Long = 0L + ) : ProtoBuf + } + + @Serializable + class MsgElemInfoServtype27( + @ProtoId(1) val videoFile: ImMsgBody.VideoFile? = null + ) : ProtoBuf + + @Serializable + class MsgElemInfoServtype29( + @ProtoId(1) val luckybagMsg: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class MsgElemInfoServtype3( + @ProtoId(1) val flashTroopPic: ImMsgBody.CustomFace? = null, + @ProtoId(2) val flashC2cPic: ImMsgBody.NotOnlineImage? = null + ) : ProtoBuf + + @Serializable + class MsgElemInfoServtype31( + @ProtoId(1) val text: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val ext: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class MsgElemInfoServtype4( + @ProtoId(1) val imsgType: Int = 0, + @ProtoId(4) val stStoryAioObjMsg: HummerCommelem.StoryAioObjMsg? = null + ) : ProtoBuf + + @Serializable + class MsgElemInfoServtype5( + @ProtoId(1) val vid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val cover: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val title: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val summary: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val createTime: Long = 0L, + @ProtoId(6) val commentContent: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(7) val author: Long = 0L + ) : ProtoBuf + + @Serializable + class MsgElemInfoServtype8( + @ProtoId(1) val wifiDeliverGiftMsg: ImMsgBody.DeliverGiftMsg? = null + ) : ProtoBuf + + @Serializable + class MsgElemInfoServtype9( + @ProtoId(1) val anchorStatus: Int = 0, + @ProtoId(2) val jumpSchema: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val anchorNickname: String = "", + @ProtoId(4) val anchorHeadUrl: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val liveTitle: String = "" + ) : ProtoBuf + + @Serializable + class StoryAioObjMsg( + @ProtoId(1) val uiUrl: String = "", + @ProtoId(2) val jmpUrl: String = "" + ) : ProtoBuf +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt index d4624d95a..214c31233 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt @@ -223,9 +223,7 @@ internal class MessageSvc { friend.lastMessageSequence.loop { instant -> if (msg.msgHead.msgSeq > instant) { - println("bigger") if (friend.lastMessageSequence.compareAndSet(instant, msg.msgHead.msgSeq)) { - println("set ok") return@mapNotNull FriendMessage( friend, msg.toMessageChain() diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/HummerMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/HummerMessage.kt new file mode 100644 index 000000000..e9fbe1f0a --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/HummerMessage.kt @@ -0,0 +1,87 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +@file:Suppress("unused") + +package net.mamoe.mirai.message.data + +import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.SinceMirai +import kotlin.jvm.JvmField + +@SinceMirai("0.31.0") +@MiraiExperimentalAPI +sealed class HummerMessage : MessageContent { + companion object Key : Message.Key +} + +/** + * 戳一戳 + */ +@MiraiExperimentalAPI +@SinceMirai("0.31.0") +@OptIn(MiraiInternalAPI::class) +class PokeMessage @MiraiInternalAPI(message = "使用伴生对象中的常量") constructor( + val type: Int, + @MiraiExperimentalAPI + val id: Int +) : HummerMessage() { + companion object Types : Message.Key { + /** + * 戳一戳 + */ + @JvmField + val Poke = PokeMessage(1, -1) + + /** + * 比心 + */ + @JvmField + val ShowLove = PokeMessage(2, -1) + + /** + * 点赞 + */ + @JvmField + val Like = PokeMessage(3, -1) + + /** + * 心碎 + */ + @JvmField + val Heartbroken = PokeMessage(4, -1) + + /** + * 666 + */ + @JvmField + val SixSixSix = PokeMessage(5, -1) + + /** + * 放大招 + */ + @JvmField + val FangDaZhao = PokeMessage(6, -1) + } + + private val stringValue = "[mirai:Poke($type, $id)]" + + override fun toString(): String = stringValue + override val length: Int get() = stringValue.length + override fun get(index: Int): Char = stringValue[index] + override fun subSequence(startIndex: Int, endIndex: Int): CharSequence = + stringValue.subSequence(startIndex, endIndex) + + override fun compareTo(other: String): Int = stringValue.compareTo(other) + + //businessType=0x00000001(1) + //pbElem=08 01 18 00 20 FF FF FF FF 0F 2A 00 32 00 38 00 50 00 + //serviceType=0x00000002(2) +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt index 04f8d8afd..1e81720f0 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt @@ -15,6 +15,7 @@ package net.mamoe.mirai.message.data import net.mamoe.mirai.message.data.NullMessageChain.equals import net.mamoe.mirai.message.data.NullMessageChain.toString +import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.SinceMirai import kotlin.js.JsName @@ -44,6 +45,10 @@ import kotlin.reflect.KProperty */ interface MessageChain : Message, Iterable { override operator fun contains(sub: String): Boolean + + /** + * 得到易读的字符串 + */ override fun toString(): String /** @@ -127,25 +132,27 @@ inline fun MessageChain.any(): Boolean = this.any { it is /** * 获取第一个 [M] 类型的 [Message] 实例 */ -@OptIn(ExperimentalMessageSource::class) +@OptIn(ExperimentalMessageSource::class, MiraiExperimentalAPI::class) @JvmSynthetic @Suppress("UNCHECKED_CAST") fun MessageChain.firstOrNull(key: Message.Key): M? = when (key) { - At -> first() - AtAll -> first() - PlainText -> first() - Image -> first<Image>() - OnlineImage -> first<OnlineImage>() - OfflineImage -> first<OfflineImage>() - GroupImage -> first<GroupImage>() - FriendImage -> first<FriendImage>() - Face -> first<Face>() - QuoteReply -> first<QuoteReply>() - MessageSource -> first<MessageSource>() - XmlMessage -> first<XmlMessage>() - JsonMessage -> first<JsonMessage>() - RichMessage -> first<RichMessage>() - LightApp -> first<LightApp>() + At -> firstOrNull<At>() + AtAll -> firstOrNull<AtAll>() + PlainText -> firstOrNull<PlainText>() + Image -> firstOrNull<Image>() + OnlineImage -> firstOrNull<OnlineImage>() + OfflineImage -> firstOrNull<OfflineImage>() + GroupImage -> firstOrNull<GroupImage>() + FriendImage -> firstOrNull<FriendImage>() + Face -> firstOrNull<Face>() + QuoteReply -> firstOrNull<QuoteReply>() + MessageSource -> firstOrNull<MessageSource>() + XmlMessage -> firstOrNull<XmlMessage>() + JsonMessage -> firstOrNull<JsonMessage>() + RichMessage -> firstOrNull<RichMessage>() + LightApp -> firstOrNull<LightApp>() + PokeMessage -> firstOrNull<PokeMessage>() + HummerMessage -> firstOrNull<HummerMessage>() else -> null } as M? diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/RichMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/RichMessage.kt index e3dc780ac..446cd4b66 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/RichMessage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/RichMessage.kt @@ -163,6 +163,13 @@ class LongMessage(override val content: String, val resId: String) : RichMessage override fun toString(): String = content } +/* +commonElem=CommonElem#750141174 { + businessType=0x00000001(1) + pbElem=08 01 18 00 20 FF FF FF FF 0F 2A 00 32 00 38 00 50 00 + serviceType=0x00000002(2) +} + */ /** * 构造一条 XML 消息