Support poke message, close #132

This commit is contained in:
Him188 2020-03-29 15:37:31 +08:00
parent f7040c18fb
commit 52f8543597
9 changed files with 618 additions and 167 deletions

View File

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

View File

@ -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<ImMsgBody.Elem> {
@ -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 <reified R> Iterable<*>.firstIsInstance(): R {
throw NoSuchElementException("Collection contains no element matching the predicate.")
}
@OptIn(MiraiInternalAPI::class, LowLevelAPI::class)
internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChainBuilder) {
this.forEach {
when {
@ -383,6 +257,22 @@ internal fun List<ImMsgBody.Elem>.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())
}
}
}

View File

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

View File

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

View File

@ -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<HummerCommelem.MsgElemInfoServtype21.GroupConfessItem>? = 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
}

View File

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

View File

@ -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<HummerMessage>
}
/**
* 戳一戳
*/
@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<PokeMessage> {
/**
* 戳一戳
*/
@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)
}

View File

@ -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<SingleMessage> {
override operator fun contains(sub: String): Boolean
/**
* 得到易读的字符串
*/
override fun toString(): String
/**
@ -127,25 +132,27 @@ inline fun <reified M : Message> MessageChain.any(): Boolean = this.any { it is
/**
* 获取第一个 [M] 类型的 [Message] 实例
*/
@OptIn(ExperimentalMessageSource::class)
@OptIn(ExperimentalMessageSource::class, MiraiExperimentalAPI::class)
@JvmSynthetic
@Suppress("UNCHECKED_CAST")
fun <M : Message> MessageChain.firstOrNull(key: Message.Key<M>): M? = when (key) {
At -> first<At>()
AtAll -> first<AtAll>()
PlainText -> first<PlainText>()
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?

View File

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