Merge remote-tracking branch 'origin/master'

# Conflicts:
#	mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt
This commit is contained in:
jiahua.liu 2020-01-30 13:25:53 +08:00
commit 3ae1832ac1
24 changed files with 621 additions and 150 deletions

View File

@ -39,8 +39,7 @@ QQ for Android 8.2.0 版本2019 年 12 月)协议的实现,目前还
- 完成 密码登录 2020/1/23 - 完成 密码登录 2020/1/23
- 完成 群消息解析 (2020/1/25 - 完成 群消息解析 (2020/1/25
- 完成 图片验证码登录 (2020/1/26 - 完成 图片验证码登录 (2020/1/26
- 进行中 免密登录 - 完成 设备锁登录 (2020/1/29)
- 进行中 SMS 登录
- 进行中 消息解析和发送 - 进行中 消息解析和发送
- 进行中 图片上传和下载 - 进行中 图片上传和下载

View File

@ -1,6 +1,5 @@
package net.mamoe.mirai.qqandroid package net.mamoe.mirai.qqandroid
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.FriendNameRemark import net.mamoe.mirai.data.FriendNameRemark
import net.mamoe.mirai.data.GroupInfo import net.mamoe.mirai.data.GroupInfo
@ -8,16 +7,19 @@ import net.mamoe.mirai.data.PreviousNameList
import net.mamoe.mirai.data.Profile import net.mamoe.mirai.data.Profile
import net.mamoe.mirai.message.data.ImageId import net.mamoe.mirai.message.data.ImageId
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
internal abstract class ContactImpl : Contact internal abstract class ContactImpl : Contact
internal class QQImpl(bot: Bot, override val coroutineContext: CoroutineContext, override val id: Long) : ContactImpl(), QQ { internal class QQImpl(bot: QQAndroidBot, override val coroutineContext: CoroutineContext, override val id: Long) : ContactImpl(), QQ {
override val bot: Bot by bot.unsafeWeakRef() override val bot: QQAndroidBot by bot.unsafeWeakRef()
override suspend fun sendMessage(message: MessageChain) { override suspend fun sendMessage(message: MessageChain) {
TODO("not implemented") bot.network.run {
MessageSvc.PbSendMsg.ToFriend(bot.client, id, message).sendAndExpect<MessageSvc.PbSendMsg.Response>()
}
} }
override suspend fun uploadImage(image: ExternalImage): ImageId { override suspend fun uploadImage(image: ExternalImage): ImageId {
@ -38,11 +40,11 @@ internal class QQImpl(bot: Bot, override val coroutineContext: CoroutineContext,
} }
internal class MemberImpl(bot: Bot, group: Group, override val coroutineContext: CoroutineContext, override val id: Long) : ContactImpl(), Member { internal class MemberImpl(bot: QQAndroidBot, group: Group, override val coroutineContext: CoroutineContext, override val id: Long) : ContactImpl(), Member {
override val group: Group by group.unsafeWeakRef() override val group: Group by group.unsafeWeakRef()
override val permission: MemberPermission override val permission: MemberPermission
get() = TODO("not implemented") get() = TODO("not implemented")
override val bot: Bot by bot.unsafeWeakRef() override val bot: QQAndroidBot by bot.unsafeWeakRef()
override suspend fun mute(durationSeconds: Int): Boolean { override suspend fun mute(durationSeconds: Int): Boolean {
TODO("not implemented") TODO("not implemented")
@ -76,7 +78,7 @@ internal class MemberImpl(bot: Bot, group: Group, override val coroutineContext:
@UseExperimental(MiraiInternalAPI::class) @UseExperimental(MiraiInternalAPI::class)
internal class GroupImpl(bot: Bot, override val coroutineContext: CoroutineContext, override val id: Long) : ContactImpl(), Group { internal class GroupImpl(bot: QQAndroidBot, override val coroutineContext: CoroutineContext, override val id: Long) : ContactImpl(), Group {
override val internalId: GroupInternalId = GroupId(id).toInternalId() override val internalId: GroupInternalId = GroupId(id).toInternalId()
override val owner: Member override val owner: Member
get() = TODO("not implemented") get() = TODO("not implemented")
@ -86,7 +88,8 @@ internal class GroupImpl(bot: Bot, override val coroutineContext: CoroutineConte
get() = TODO("not implemented") get() = TODO("not implemented")
override val members: ContactList<Member> = ContactList(LockFreeLinkedList()) override val members: ContactList<Member> = ContactList(LockFreeLinkedList())
override fun getMember(id: Long): Member = members.delegate.filteringGetOrAdd({ it.id == id }, { MemberImpl(bot, this, coroutineContext, id) }) override fun getMember(id: Long): Member =
members.delegate.filteringGetOrAdd({ it.id == id }, { MemberImpl(bot as QQAndroidBot, this, coroutineContext, id) })
override suspend fun updateGroupInfo(): GroupInfo { override suspend fun updateGroupInfo(): GroupInfo {
TODO("not implemented") TODO("not implemented")
@ -96,7 +99,7 @@ internal class GroupImpl(bot: Bot, override val coroutineContext: CoroutineConte
TODO("not implemented") TODO("not implemented")
} }
override val bot: Bot by bot.unsafeWeakRef() override val bot: QQAndroidBot by bot.unsafeWeakRef()
override suspend fun sendMessage(message: MessageChain) { override suspend fun sendMessage(message: MessageChain) {
TODO("not implemented") TODO("not implemented")

View File

@ -33,7 +33,7 @@ internal abstract class QQAndroidBotBase constructor(
override val qqs: ContactList<QQ> = ContactList(LockFreeLinkedList()) override val qqs: ContactList<QQ> = ContactList(LockFreeLinkedList())
override fun getQQ(id: Long): QQ { override fun getQQ(id: Long): QQ {
return qqs.delegate.filteringGetOrAdd({ it.id == id }, { QQImpl(this, coroutineContext, id) }) return qqs.delegate.filteringGetOrAdd({ it.id == id }, { QQImpl(this as QQAndroidBot, coroutineContext, id) })
} }
override fun createNetworkHandler(coroutineContext: CoroutineContext): QQAndroidBotNetworkHandler { override fun createNetworkHandler(coroutineContext: CoroutineContext): QQAndroidBotNetworkHandler {
@ -43,17 +43,17 @@ internal abstract class QQAndroidBotBase constructor(
override val groups: ContactList<Group> = ContactList(LockFreeLinkedList()) override val groups: ContactList<Group> = ContactList(LockFreeLinkedList())
override suspend fun getGroup(id: GroupId): Group { override suspend fun getGroup(id: GroupId): Group {
return groups.delegate.filteringGetOrAdd({ it.id == id.value }, { GroupImpl(this, coroutineContext, id.value) }) return groups.delegate.filteringGetOrAdd({ it.id == id.value }, { GroupImpl(this as QQAndroidBot, coroutineContext, id.value) })
} }
override suspend fun getGroup(internalId: GroupInternalId): Group { override suspend fun getGroup(internalId: GroupInternalId): Group {
internalId.toId().value.let { id -> internalId.toId().value.let { id ->
return groups.delegate.filteringGetOrAdd({ it.id == id }, { GroupImpl(this, coroutineContext, id) }) return groups.delegate.filteringGetOrAdd({ it.id == id }, { GroupImpl(this as QQAndroidBot, coroutineContext, id) })
} }
} }
override suspend fun getGroup(id: Long): Group { override suspend fun getGroup(id: Long): Group {
return groups.delegate.filteringGetOrAdd({ it.id == id }, { GroupImpl(this, coroutineContext, id) }) return groups.delegate.filteringGetOrAdd({ it.id == id }, { GroupImpl(this as QQAndroidBot, coroutineContext, id) })
} }
override suspend fun Image.getLink(): ImageLink { override suspend fun Image.getLink(): ImageLink {

View File

@ -7,7 +7,7 @@ import net.mamoe.mirai.event.events.BotEvent
/** /**
* 被挤下线 * 被挤下线
*/ */
class ForceOfflineEvent( data class ForceOfflineEvent(
override val bot: Bot, override val bot: Bot,
val title: String, val title: String,
val tips: String val tips: String

View File

@ -6,6 +6,8 @@ import kotlinx.io.core.readBytes
import kotlinx.io.core.writeFully import kotlinx.io.core.writeFully
import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.SerializationStrategy
import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport
import net.mamoe.mirai.utils.io.toUHexString
/** /**
* 仅有标示作用 * 仅有标示作用
@ -13,26 +15,29 @@ import kotlinx.serialization.SerializationStrategy
interface ProtoBuf interface ProtoBuf
fun <T : ProtoBuf> BytePacketBuilder.writeProtoBuf(serializer: SerializationStrategy<T>, v: T) { fun <T : ProtoBuf> BytePacketBuilder.writeProtoBuf(serializer: SerializationStrategy<T>, v: T) {
this.writeFully(v.toByteArray(serializer))
this.writeFully(v.toByteArray(serializer).also {
println("发送 protobuf: ${it.toUHexString()}")
})
} }
/** /**
* dump * dump
*/ */
fun <T : ProtoBuf> T.toByteArray(serializer: SerializationStrategy<T>): ByteArray { fun <T : ProtoBuf> T.toByteArray(serializer: SerializationStrategy<T>): ByteArray {
return kotlinx.serialization.protobuf.ProtoBuf.dump(serializer, this) return ProtoBufWithNullableSupport.dump(serializer, this)
} }
/** /**
* load * load
*/ */
fun <T : ProtoBuf> ByteArray.loadAs(deserializer: DeserializationStrategy<T>): T { fun <T : ProtoBuf> ByteArray.loadAs(deserializer: DeserializationStrategy<T>): T {
return kotlinx.serialization.protobuf.ProtoBuf.load(deserializer, this) return ProtoBufWithNullableSupport.load(deserializer, this)
} }
/** /**
* load * load
*/ */
fun <T : ProtoBuf> Input.readRemainingAsProtoBuf(serializer: DeserializationStrategy<T>): T { fun <T : ProtoBuf> Input.readRemainingAsProtoBuf(serializer: DeserializationStrategy<T>): T {
return kotlinx.serialization.protobuf.ProtoBuf.load(serializer, this.readBytes()) return ProtoBufWithNullableSupport.load(serializer, this.readBytes())
} }

View File

@ -191,7 +191,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
this.writeHead(STRUCT_END, 0) this.writeHead(STRUCT_END, 0)
} }
} else if (value is ProtoBuf) { } else if (value is ProtoBuf) {
this.encodeTaggedByteArray(popTag(), kotlinx.serialization.protobuf.ProtoBuf.dump(value)) this.encodeTaggedByteArray(popTag(), net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport.dump(value))
} else { } else {
serializer.serialize(this, value) serializer.serialize(this, value)
} }

View File

@ -0,0 +1,483 @@
/*
* Copyright 2017-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package net.mamoe.mirai.qqandroid.io.serialization
import kotlinx.io.*
import kotlinx.serialization.*
import kotlinx.serialization.CompositeDecoder.Companion.READ_DONE
import kotlinx.serialization.internal.*
import kotlinx.serialization.modules.EmptyModule
import kotlinx.serialization.modules.SerialModule
import kotlinx.serialization.protobuf.ProtoNumberType
import kotlinx.serialization.protobuf.ProtoType
import kotlinx.serialization.protobuf.ProtobufDecodingException
import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport.Varint.decodeSignedVarintInt
import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport.Varint.decodeSignedVarintLong
import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport.Varint.decodeVarint
import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport.Varint.encodeVarint
internal typealias ProtoDesc = Pair<Int, ProtoNumberType>
internal fun extractParameters(desc: SerialDescriptor, index: Int, zeroBasedDefault: Boolean = false): ProtoDesc {
val idx = getSerialId(desc, index) ?: (if (zeroBasedDefault) index else index + 1)
val format = desc.findAnnotation<ProtoType>(index)?.type
?: ProtoNumberType.DEFAULT
return idx to format
}
class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : AbstractSerialFormat(context), BinaryFormat {
internal open inner class ProtobufWriter(val encoder: ProtobufEncoder) : TaggedEncoder<ProtoDesc>() {
public override val context
get() = this@ProtoBufWithNullableSupport.context
override fun beginStructure(desc: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeEncoder = when (desc.kind) {
StructureKind.LIST -> RepeatedWriter(encoder, currentTag)
StructureKind.CLASS, UnionKind.OBJECT, is PolymorphicKind -> ObjectWriter(currentTagOrNull, encoder)
StructureKind.MAP -> MapRepeatedWriter(currentTagOrNull, encoder)
else -> throw SerializationException("Primitives are not supported at top-level")
}
override fun encodeTaggedInt(tag: ProtoDesc, value: Int) = encoder.writeInt(value, tag.first, tag.second)
override fun encodeTaggedByte(tag: ProtoDesc, value: Byte) = encoder.writeInt(value.toInt(), tag.first, tag.second)
override fun encodeTaggedShort(tag: ProtoDesc, value: Short) = encoder.writeInt(value.toInt(), tag.first, tag.second)
override fun encodeTaggedLong(tag: ProtoDesc, value: Long) = encoder.writeLong(value, tag.first, tag.second)
override fun encodeTaggedFloat(tag: ProtoDesc, value: Float) = encoder.writeFloat(value, tag.first)
override fun encodeTaggedDouble(tag: ProtoDesc, value: Double) = encoder.writeDouble(value, tag.first)
override fun encodeTaggedBoolean(tag: ProtoDesc, value: Boolean) = encoder.writeInt(if (value) 1 else 0, tag.first, ProtoNumberType.DEFAULT)
override fun encodeTaggedChar(tag: ProtoDesc, value: Char) = encoder.writeInt(value.toInt(), tag.first, tag.second)
override fun encodeTaggedString(tag: ProtoDesc, value: String) = encoder.writeString(value, tag.first)
override fun encodeTaggedEnum(
tag: ProtoDesc,
enumDescription: SerialDescriptor,
ordinal: Int
) = encoder.writeInt(
extractParameters(enumDescription, ordinal, zeroBasedDefault = true).first,
tag.first,
ProtoNumberType.DEFAULT
)
override fun SerialDescriptor.getTag(index: Int) = this.getProtoDesc(index)
// MIRAI MODIFY START:
override fun encodeTaggedNull(tag: ProtoDesc) {
}
override fun <T : Any> encodeNullableSerializableValue(serializer: SerializationStrategy<T>, value: T?) {
if (value == null) {
encodeTaggedNull(popTag())
} else encodeSerializableValue(serializer, value)
}
// MIRAI MODIFY END
@Suppress("UNCHECKED_CAST", "NAME_SHADOWING")
override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) = when {
// encode maps as collection of map entries, not merged collection of key-values
serializer.descriptor is MapLikeDescriptor -> {
val serializer = (serializer as MapLikeSerializer<Any?, Any?, T, *>)
val mapEntrySerial = MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer)
HashSetSerializer(mapEntrySerial).serialize(this, (value as Map<*, *>).entries)
}
serializer.descriptor == ByteArraySerializer.descriptor -> encoder.writeBytes(value as ByteArray, popTag().first)
else -> serializer.serialize(this, value)
}
}
internal open inner class ObjectWriter(
val parentTag: ProtoDesc?, private val parentEncoder: ProtobufEncoder,
private val stream: ByteArrayOutputStream = ByteArrayOutputStream()
) : ProtobufWriter(ProtobufEncoder(stream)) {
override fun endEncode(desc: SerialDescriptor) {
if (parentTag != null) {
parentEncoder.writeBytes(stream.toByteArray(), parentTag.first)
} else {
parentEncoder.out.write(stream.toByteArray())
}
}
}
internal inner class MapRepeatedWriter(parentTag: ProtoDesc?, parentEncoder: ProtobufEncoder) : ObjectWriter(parentTag, parentEncoder) {
override fun SerialDescriptor.getTag(index: Int): ProtoDesc =
if (index % 2 == 0) 1 to (parentTag?.second ?: ProtoNumberType.DEFAULT)
else 2 to (parentTag?.second ?: ProtoNumberType.DEFAULT)
}
internal inner class RepeatedWriter(encoder: ProtobufEncoder, val curTag: ProtoDesc) : ProtobufWriter(encoder) {
override fun SerialDescriptor.getTag(index: Int) = curTag
}
internal class ProtobufEncoder(val out: ByteArrayOutputStream) {
fun writeBytes(bytes: ByteArray, tag: Int) {
val header = encode32((tag shl 3) or SIZE_DELIMITED)
val len = encode32(bytes.size)
out.write(header)
out.write(len)
out.write(bytes)
}
fun writeInt(value: Int, tag: Int, format: ProtoNumberType) {
val wireType = if (format == ProtoNumberType.FIXED) i32 else VARINT
val header = encode32((tag shl 3) or wireType)
val content = encode32(value, format)
out.write(header)
out.write(content)
}
fun writeLong(value: Long, tag: Int, format: ProtoNumberType) {
val wireType = if (format == ProtoNumberType.FIXED) i64 else VARINT
val header = encode32((tag shl 3) or wireType)
val content = encode64(value, format)
out.write(header)
out.write(content)
}
fun writeString(value: String, tag: Int) {
val bytes = value.toUtf8Bytes()
writeBytes(bytes, tag)
}
fun writeDouble(value: Double, tag: Int) {
val header = encode32((tag shl 3) or i64)
val content = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putDouble(value).array()
out.write(header)
out.write(content)
}
fun writeFloat(value: Float, tag: Int) {
val header = encode32((tag shl 3) or i32)
val content = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putFloat(value).array()
out.write(header)
out.write(content)
}
private fun encode32(number: Int, format: ProtoNumberType = ProtoNumberType.DEFAULT): ByteArray =
when (format) {
ProtoNumberType.FIXED -> ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(number).array()
ProtoNumberType.DEFAULT -> encodeVarint(number.toLong())
ProtoNumberType.SIGNED -> encodeVarint(((number shl 1) xor (number shr 31)))
}
private fun encode64(number: Long, format: ProtoNumberType = ProtoNumberType.DEFAULT): ByteArray =
when (format) {
ProtoNumberType.FIXED -> ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(number).array()
ProtoNumberType.DEFAULT -> encodeVarint(number)
ProtoNumberType.SIGNED -> encodeVarint((number shl 1) xor (number shr 63))
}
}
private open inner class ProtobufReader(val decoder: ProtobufDecoder) : TaggedDecoder<ProtoDesc>() {
override val context: SerialModule
get() = this@ProtoBufWithNullableSupport.context
private val indexByTag: MutableMap<Int, Int> = mutableMapOf()
private fun findIndexByTag(desc: SerialDescriptor, serialId: Int, zeroBasedDefault: Boolean = false): Int =
(0 until desc.elementsCount).firstOrNull {
extractParameters(
desc,
it,
zeroBasedDefault
).first == serialId
} ?: -1
override fun beginStructure(desc: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder = when (desc.kind) {
StructureKind.LIST -> RepeatedReader(decoder, currentTag)
StructureKind.CLASS, UnionKind.OBJECT, is PolymorphicKind ->
ProtobufReader(makeDelimited(decoder, currentTagOrNull))
StructureKind.MAP -> MapEntryReader(makeDelimited(decoder, currentTagOrNull), currentTagOrNull)
else -> throw SerializationException("Primitives are not supported at top-level")
}
override fun decodeTaggedBoolean(tag: ProtoDesc): Boolean = when (val i = decoder.nextInt(ProtoNumberType.DEFAULT)) {
0 -> false
1 -> true
else -> throw ProtobufDecodingException("Expected boolean value (0 or 1), found $i")
}
override fun decodeTaggedByte(tag: ProtoDesc): Byte = decoder.nextInt(tag.second).toByte()
override fun decodeTaggedShort(tag: ProtoDesc): Short = decoder.nextInt(tag.second).toShort()
override fun decodeTaggedInt(tag: ProtoDesc): Int = decoder.nextInt(tag.second)
override fun decodeTaggedLong(tag: ProtoDesc): Long = decoder.nextLong(tag.second)
override fun decodeTaggedFloat(tag: ProtoDesc): Float = decoder.nextFloat()
override fun decodeTaggedDouble(tag: ProtoDesc): Double = decoder.nextDouble()
override fun decodeTaggedChar(tag: ProtoDesc): Char = decoder.nextInt(tag.second).toChar()
override fun decodeTaggedString(tag: ProtoDesc): String = decoder.nextString()
override fun decodeTaggedEnum(tag: ProtoDesc, enumDescription: SerialDescriptor): Int =
findIndexByTag(enumDescription, decoder.nextInt(ProtoNumberType.DEFAULT), zeroBasedDefault = true)
@Suppress("UNCHECKED_CAST")
override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T = when {
// encode maps as collection of map entries, not merged collection of key-values
deserializer.descriptor is MapLikeDescriptor -> {
val serializer = (deserializer as MapLikeSerializer<Any?, Any?, T, *>)
val mapEntrySerial = MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer)
val setOfEntries = HashSetSerializer(mapEntrySerial).deserialize(this)
setOfEntries.associateBy({ it.key }, { it.value }) as T
}
deserializer.descriptor == ByteArraySerializer.descriptor -> decoder.nextObject() as T
else -> deserializer.deserialize(this)
}
override fun SerialDescriptor.getTag(index: Int) = this.getProtoDesc(index)
override fun decodeElementIndex(desc: SerialDescriptor): Int {
while (true) {
if (decoder.curId == -1) // EOF
return READ_DONE
val ind = indexByTag.getOrPut(decoder.curId) { findIndexByTag(desc, decoder.curId) }
if (ind == -1) // not found
decoder.skipElement()
else return ind
}
}
}
private inner class RepeatedReader(decoder: ProtobufDecoder, val targetTag: ProtoDesc) : ProtobufReader(decoder) {
private var ind = -1
override fun decodeElementIndex(desc: SerialDescriptor) = if (decoder.curId == targetTag.first) ++ind else READ_DONE
override fun SerialDescriptor.getTag(index: Int): ProtoDesc = targetTag
}
private inner class MapEntryReader(decoder: ProtobufDecoder, val parentTag: ProtoDesc?) : ProtobufReader(decoder) {
override fun SerialDescriptor.getTag(index: Int): ProtoDesc =
if (index % 2 == 0) 1 to (parentTag?.second ?: ProtoNumberType.DEFAULT)
else 2 to (parentTag?.second ?: ProtoNumberType.DEFAULT)
}
internal class ProtobufDecoder(val inp: ByteArrayInputStream) {
val curId
get() = curTag.first
private var curTag: Pair<Int, Int> = -1 to -1
init {
readTag()
}
private fun readTag(): Pair<Int, Int> {
val header = decode32(eofAllowed = true)
curTag = if (header == -1) {
-1 to -1
} else {
val wireType = header and 0b111
val fieldId = header ushr 3
fieldId to wireType
}
return curTag
}
fun skipElement() {
when (curTag.second) {
VARINT -> nextInt(ProtoNumberType.DEFAULT)
i64 -> nextLong(ProtoNumberType.FIXED)
SIZE_DELIMITED -> nextObject()
i32 -> nextInt(ProtoNumberType.FIXED)
else -> throw ProtobufDecodingException("Unsupported start group or end group wire type")
}
}
@Suppress("NOTHING_TO_INLINE")
private inline fun assertWireType(expected: Int) {
if (curTag.second != expected) throw ProtobufDecodingException("Expected wire type $expected, but found ${curTag.second}")
}
fun nextObject(): ByteArray {
assertWireType(SIZE_DELIMITED)
val len = decode32()
check(len >= 0)
val ans = inp.readExactNBytes(len)
readTag()
return ans
}
fun nextInt(format: ProtoNumberType): Int {
val wireType = if (format == ProtoNumberType.FIXED) i32 else VARINT
assertWireType(wireType)
val ans = decode32(format)
readTag()
return ans
}
fun nextLong(format: ProtoNumberType): Long {
val wireType = if (format == ProtoNumberType.FIXED) i64 else VARINT
assertWireType(wireType)
val ans = decode64(format)
readTag()
return ans
}
fun nextFloat(): Float {
assertWireType(i32)
val ans = inp.readToByteBuffer(4).order(ByteOrder.LITTLE_ENDIAN).getFloat()
readTag()
return ans
}
fun nextDouble(): Double {
assertWireType(i64)
val ans = inp.readToByteBuffer(8).order(ByteOrder.LITTLE_ENDIAN).getDouble()
readTag()
return ans
}
fun nextString(): String {
val bytes = this.nextObject()
return stringFromUtf8Bytes(bytes)
}
private fun decode32(format: ProtoNumberType = ProtoNumberType.DEFAULT, eofAllowed: Boolean = false): Int = when (format) {
ProtoNumberType.DEFAULT -> decodeVarint(inp, 64, eofAllowed).toInt()
ProtoNumberType.SIGNED -> decodeSignedVarintInt(inp)
ProtoNumberType.FIXED -> inp.readToByteBuffer(4).order(ByteOrder.LITTLE_ENDIAN).getInt()
}
private fun decode64(format: ProtoNumberType = ProtoNumberType.DEFAULT): Long = when (format) {
ProtoNumberType.DEFAULT -> decodeVarint(inp, 64)
ProtoNumberType.SIGNED -> decodeSignedVarintLong(inp)
ProtoNumberType.FIXED -> inp.readToByteBuffer(8).order(ByteOrder.LITTLE_ENDIAN).getLong()
}
}
/**
* Source for all varint operations:
* https://github.com/addthis/stream-lib/blob/master/src/main/java/com/clearspring/analytics/util/Varint.java
*/
internal object Varint {
internal fun encodeVarint(inp: Int): ByteArray {
var value = inp
val byteArrayList = ByteArray(10)
var i = 0
while (value and 0xFFFFFF80.toInt() != 0) {
byteArrayList[i++] = ((value and 0x7F) or 0x80).toByte()
value = value ushr 7
}
byteArrayList[i] = (value and 0x7F).toByte()
val out = ByteArray(i + 1)
while (i >= 0) {
out[i] = byteArrayList[i]
i--
}
return out
}
internal fun encodeVarint(inp: Long): ByteArray {
var value = inp
val byteArrayList = ByteArray(10)
var i = 0
while (value and 0x7FL.inv() != 0L) {
byteArrayList[i++] = ((value and 0x7F) or 0x80).toByte()
value = value ushr 7
}
byteArrayList[i] = (value and 0x7F).toByte()
val out = ByteArray(i + 1)
while (i >= 0) {
out[i] = byteArrayList[i]
i--
}
return out
}
internal fun decodeVarint(inp: InputStream, bitLimit: Int = 32, eofOnStartAllowed: Boolean = false): Long {
var result = 0L
var shift = 0
var b: Int
do {
if (shift >= bitLimit) {
// Out of range
throw ProtobufDecodingException("Varint too long: exceeded $bitLimit bits")
}
// Get 7 bits from next byte
b = inp.read()
if (b == -1) {
if (eofOnStartAllowed && shift == 0) return -1
else throw IOException("Unexpected EOF")
}
result = result or (b.toLong() and 0x7FL shl shift)
shift += 7
} while (b and 0x80 != 0)
return result
}
internal fun decodeSignedVarintInt(inp: InputStream): Int {
val raw = decodeVarint(inp, 32).toInt()
val temp = raw shl 31 shr 31 xor raw shr 1
// This extra step lets us deal with the largest signed values by treating
// negative results from read unsigned methods as like unsigned values.
// Must re-flip the top bit if the original read value had it set.
return temp xor (raw and (1 shl 31))
}
internal fun decodeSignedVarintLong(inp: InputStream): Long {
val raw = decodeVarint(inp, 64)
val temp = raw shl 63 shr 63 xor raw shr 1
// This extra step lets us deal with the largest signed values by treating
// negative results from read unsigned methods as like unsigned values
// Must re-flip the top bit if the original read value had it set.
return temp xor (raw and (1L shl 63))
}
}
companion object : BinaryFormat {
public override val context: SerialModule get() = plain.context
// todo: make more memory-efficient
private fun makeDelimited(decoder: ProtobufDecoder, parentTag: ProtoDesc?): ProtobufDecoder {
if (parentTag == null) return decoder
val bytes = decoder.nextObject()
return ProtobufDecoder(ByteArrayInputStream(bytes))
}
private fun SerialDescriptor.getProtoDesc(index: Int): ProtoDesc {
return extractParameters(this, index)
}
internal const val VARINT = 0
internal const val i64 = 1
internal const val SIZE_DELIMITED = 2
internal const val i32 = 5
val plain = ProtoBufWithNullableSupport()
override fun <T> dump(serializer: SerializationStrategy<T>, obj: T): ByteArray = plain.dump(serializer, obj)
override fun <T> load(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T = plain.load(deserializer, bytes)
override fun install(module: SerialModule) = throw IllegalStateException("You should not install anything to global instance")
}
override fun <T> dump(serializer: SerializationStrategy<T>, obj: T): ByteArray {
val encoder = ByteArrayOutputStream()
val dumper = ProtobufWriter(ProtobufEncoder(encoder))
dumper.encode(serializer, obj)
return encoder.toByteArray()
}
override fun <T> load(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T {
val stream = ByteArrayInputStream(bytes)
val reader = ProtobufReader(ProtobufDecoder(stream))
return reader.decode(deserializer)
}
}
internal fun InputStream.readExactNBytes(bytes: Int): ByteArray {
val array = ByteArray(bytes)
var read = 0
while (read < bytes) {
val i = this.read(array, read, bytes - read)
if (i == -1) throw IOException("Unexpected EOF")
read += i
}
return array
}
internal fun InputStream.readToByteBuffer(bytes: Int): ByteBuffer {
val arr = readExactNBytes(bytes)
val buf = ByteBuffer.allocate(bytes)
buf.put(arr).flip()
return buf
}

View File

@ -314,10 +314,14 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
suspend fun <E : Packet> OutgoingPacket.sendAndExpect(): E { suspend fun <E : Packet> OutgoingPacket.sendAndExpect(): E {
val handler = PacketListener(commandName = commandName, sequenceId = sequenceId) val handler = PacketListener(commandName = commandName, sequenceId = sequenceId)
packetListeners.addLast(handler) packetListeners.addLast(handler)
bot.logger.info("Send: ${this.commandName}")
channel.send(delegate) channel.send(delegate)
return withTimeout(3000) { return withTimeoutOrNull(3000) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
handler.await() as E handler.await() as E
} ?: net.mamoe.mirai.qqandroid.utils.inline {
packetListeners.remove(handler)
error("timeout when sending ${this.commandName}")
} }
} }

View File

@ -469,7 +469,7 @@ class ImMsgBody : ProtoBuf {
@SerialId(16) val bubbleSubId: Int = 0, @SerialId(16) val bubbleSubId: Int = 0,
@SerialId(17) val pendantId: Long = 0L, @SerialId(17) val pendantId: Long = 0L,
@SerialId(18) val rpIndex: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(18) val rpIndex: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(19) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY @SerialId(19) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY // 78 00 F8 01 00 C8 02 00
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
@ -609,7 +609,7 @@ class ImMsgBody : ProtoBuf {
@SerialId(8) val reserved: Int = 0, @SerialId(8) val reserved: Int = 0,
@SerialId(9) val subcmd: Int = 0, @SerialId(9) val subcmd: Int = 0,
@SerialId(10) val microCloud: Int = 0, @SerialId(10) val microCloud: Int = 0,
@SerialId(11) val bytesFileUrls: List<ByteArray>? = null, @SerialId(11) val bytesFileUrls: List<ByteArray>? = listOf(),
@SerialId(12) val downloadFlag: Int = 0, @SerialId(12) val downloadFlag: Int = 0,
@SerialId(50) val dangerEvel: Int = 0, @SerialId(50) val dangerEvel: Int = 0,
@SerialId(51) val lifeTime: Int = 0, @SerialId(51) val lifeTime: Int = 0,
@ -705,7 +705,7 @@ class ImMsgBody : ProtoBuf {
@SerialId(20) val downPara: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(20) val downPara: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(29) val format: Int = 0, @SerialId(29) val format: Int = 0,
@SerialId(30) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(30) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(31) val bytesPttUrls: List<ByteArray>? = null, @SerialId(31) val bytesPttUrls: List<ByteArray>? = listOf(),
@SerialId(32) val downloadFlag: Int = 0 @SerialId(32) val downloadFlag: Int = 0
) : ProtoBuf ) : ProtoBuf
@ -813,8 +813,8 @@ class ImMsgBody : ProtoBuf {
class RichText( class RichText(
@SerialId(1) val attr: Attr? = null, @SerialId(1) val attr: Attr? = null,
@SerialId(2) val elems: MutableList<Elem> = mutableListOf(), @SerialId(2) val elems: MutableList<Elem> = mutableListOf(),
@SerialId(3) val notOnlineFile: NotOnlineFile? = null, @SerialId(3) val notOnlineFile: NotOnlineFile? =null,
@SerialId(4) val ptt: Ptt? = null, @SerialId(4) val ptt: Ptt? =null,
@SerialId(5) val tmpPtt: TmpPtt? = null, @SerialId(5) val tmpPtt: TmpPtt? = null,
@SerialId(6) val trans211TmpMsg: Trans211TmpMsg? = null @SerialId(6) val trans211TmpMsg: Trans211TmpMsg? = null
) : ProtoBuf ) : ProtoBuf
@ -1045,9 +1045,9 @@ class ImMsgHead : ProtoBuf {
@Serializable @Serializable
class InstCtrl( class InstCtrl(
@SerialId(1) val msgSendToInst: List<InstInfo>? = null, @SerialId(1) val msgSendToInst: List<InstInfo>? = listOf(),
@SerialId(2) val msgExcludeInst: List<InstInfo>? = null, @SerialId(2) val msgExcludeInst: List<InstInfo>? = listOf(),
@SerialId(3) val msgFromInst: InstInfo? = null @SerialId(3) val msgFromInst: InstInfo? = InstInfo()
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
@ -1107,7 +1107,7 @@ class ImReceipt : ProtoBuf {
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class ReceiptInfo( data class ReceiptInfo(
@SerialId(1) val readTime: Long = 0L @SerialId(1) val readTime: Long = 0L
) : ProtoBuf ) : ProtoBuf
@ -1118,7 +1118,7 @@ class ImReceipt : ProtoBuf {
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class ReceiptResp( data class ReceiptResp(
@SerialId(1) val command: Int /* enum */ = 1, @SerialId(1) val command: Int /* enum */ = 1,
@SerialId(2) val receiptInfo: ReceiptInfo? = null @SerialId(2) val receiptInfo: ReceiptInfo? = null
) : ProtoBuf ) : ProtoBuf

View File

@ -2,6 +2,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.proto
import kotlinx.serialization.SerialId import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.io.ProtoBuf import net.mamoe.mirai.qqandroid.io.ProtoBuf
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
@ -102,7 +103,7 @@ class MsgSvc : ProtoBuf {
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class MsgSendInfo( data class MsgSendInfo(
@SerialId(1) val receiver: Int = 0 @SerialId(1) val receiver: Int = 0
) : ProtoBuf ) : ProtoBuf
@ -440,7 +441,7 @@ class MsgSvc : ProtoBuf {
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class PbSendMsgResp( data class PbSendMsgResp(
@SerialId(1) val result: Int = 0, @SerialId(1) val result: Int = 0,
@SerialId(2) val errmsg: String = "", @SerialId(2) val errmsg: String = "",
@SerialId(3) val sendTime: Int = 0, @SerialId(3) val sendTime: Int = 0,
@ -450,7 +451,7 @@ class MsgSvc : ProtoBuf {
@SerialId(7) val transSvrInfo: TransSvrInfo? = null, @SerialId(7) val transSvrInfo: TransSvrInfo? = null,
@SerialId(8) val receiptResp: ImReceipt.ReceiptResp? = null, @SerialId(8) val receiptResp: ImReceipt.ReceiptResp? = null,
@SerialId(9) val textAnalysisResult: Int = 0 @SerialId(9) val textAnalysisResult: Int = 0
) : ProtoBuf ) : ProtoBuf, Packet
@Serializable @Serializable
class PbBindUinUnReadMsgNumResp( class PbBindUinUnReadMsgNumResp(

View File

@ -1,9 +1,9 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet package net.mamoe.mirai.qqandroid.network.protocol.packet
import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.protobuf.ProtoBuf import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport
interface MessageMicro interface MessageMicro
fun <T : MessageMicro> T.toByteArray(serializer: SerializationStrategy<T>): ByteArray = ProtoBuf.dump(serializer, this) fun <T : MessageMicro> T.toByteArray(serializer: SerializationStrategy<T>): ByteArray = ProtoBufWithNullableSupport.dump(serializer, this)

View File

@ -62,7 +62,8 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
OnlinePush.PbPushGroupMsg, OnlinePush.PbPushGroupMsg,
MessageSvc.PushNotify, MessageSvc.PushNotify,
MessageSvc.PbGetMsg, MessageSvc.PbGetMsg,
MessageSvc.PushForceOffline MessageSvc.PushForceOffline,
MessageSvc.PbSendMsg
) { ) {
// SvcReqMSFLoginNotify 自己的其他设备上限 // SvcReqMSFLoginNotify 自己的其他设备上限
// MessageSvc.PushReaded 电脑阅读了别人的消息, 告知手机 // MessageSvc.PushReaded 电脑阅读了别人的消息, 告知手机

View File

@ -2,7 +2,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.writeFully import kotlinx.io.core.writeFully
import kotlinx.serialization.protobuf.ProtoBuf import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport
import net.mamoe.mirai.data.Packet import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
@ -19,7 +19,7 @@ internal object ImageDownPacket : PacketFactory<ImageDownPacket.ImageDownPacketR
// TODO: 2020/1/24 测试: bodyType, subAppId // TODO: 2020/1/24 测试: bodyType, subAppId
return buildLoginOutgoingPacket(client, key = client.wLoginSigInfo.d2Key, bodyType = 1) { return buildLoginOutgoingPacket(client, key = client.wLoginSigInfo.d2Key, bodyType = 1) {
writeSsoPacket(client, subAppId = 0, commandName = commandName, sequenceId = it) { writeSsoPacket(client, subAppId = 0, commandName = commandName, sequenceId = it) {
val data = ProtoBuf.dump( val data = ProtoBufWithNullableSupport.dump(
Cmd0x352Packet.serializer(), Cmd0x352Packet.serializer(),
Cmd0x352Packet.createByImageRequest(req) Cmd0x352Packet.createByImageRequest(req)
) )

View File

@ -2,7 +2,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.writeFully import kotlinx.io.core.writeFully
import kotlinx.serialization.protobuf.ProtoBuf import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport
import net.mamoe.mirai.data.Packet import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
@ -19,7 +19,7 @@ internal object ImageUpPacket : PacketFactory<ImageUpPacket.ImageUpPacketRespons
// TODO: 2020/1/24 测试: bodyType, subAppId // TODO: 2020/1/24 测试: bodyType, subAppId
return buildLoginOutgoingPacket(client, key = client.wLoginSigInfo.d2Key, bodyType = 1) { return buildLoginOutgoingPacket(client, key = client.wLoginSigInfo.d2Key, bodyType = 1) {
writeSsoPacket(client, subAppId = 0, commandName = ImageDownPacket.commandName, sequenceId = it) { writeSsoPacket(client, subAppId = 0, commandName = ImageDownPacket.commandName, sequenceId = it) {
val data = ProtoBuf.dump( val data = ProtoBufWithNullableSupport.dump(
Cmd0x352Packet.serializer(), Cmd0x352Packet.serializer(),
Cmd0x352Packet.createByImageRequest(req) Cmd0x352Packet.createByImageRequest(req)
) )

View File

@ -2,8 +2,11 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import kotlinx.io.core.writeFully
import net.mamoe.mirai.data.MultiPacket import net.mamoe.mirai.data.MultiPacket
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.message.FriendMessage import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.event.ForceOfflineEvent import net.mamoe.mirai.qqandroid.event.ForceOfflineEvent
import net.mamoe.mirai.qqandroid.io.readRemainingAsProtoBuf import net.mamoe.mirai.qqandroid.io.readRemainingAsProtoBuf
@ -12,18 +15,23 @@ import net.mamoe.mirai.qqandroid.io.writeProtoBuf
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushForceOffline import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushForceOffline
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushNotify import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushNotify
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
import net.mamoe.mirai.qqandroid.utils.toMessageChain import net.mamoe.mirai.qqandroid.utils.toMessageChain
import net.mamoe.mirai.qqandroid.utils.toRichTextElems
import net.mamoe.mirai.utils.cryptor.contentToString import net.mamoe.mirai.utils.cryptor.contentToString
import net.mamoe.mirai.utils.io.hexToBytes import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.toReadPacket import net.mamoe.mirai.utils.io.toReadPacket
import kotlin.math.absoluteValue
import kotlin.random.Random
class MessageSvc { class MessageSvc {
/** /**
* 告知要刷新消息 * 告知要刷新好友消息
*/ */
internal object PushNotify : PacketFactory<RequestPushNotify>("MessageSvc.PushNotify") { internal object PushNotify : PacketFactory<RequestPushNotify>("MessageSvc.PushNotify") {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): RequestPushNotify { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): RequestPushNotify {
@ -41,7 +49,7 @@ class MessageSvc {
/** /**
* 进行刷新消息 * 获取好友消息和消息记录
*/ */
internal object PbGetMsg : PacketFactory<MultiPacket<FriendMessage>>("MessageSvc.PbGetMsg") { internal object PbGetMsg : PacketFactory<MultiPacket<FriendMessage>>("MessageSvc.PbGetMsg") {
val EXTRA_DATA = val EXTRA_DATA =
@ -65,9 +73,10 @@ class MessageSvc {
onlineSyncFlag = 1, onlineSyncFlag = 1,
// serverBuf = from.serverBuf ?: EMPTY_BYTE_ARRAY, // serverBuf = from.serverBuf ?: EMPTY_BYTE_ARRAY,
syncCookie = client.c2cMessageSync.syncCookie, syncCookie = client.c2cMessageSync.syncCookie,
syncFlag = client.c2cMessageSync.syncFlag, syncFlag = 1
msgCtrlBuf = client.c2cMessageSync.msgCtrlBuf, // syncFlag = client.c2cMessageSync.syncFlag,
pubaccountCookie = client.c2cMessageSync.pubAccountCookie //msgCtrlBuf = client.c2cMessageSync.msgCtrlBuf,
//pubaccountCookie = client.c2cMessageSync.pubAccountCookie
) )
) )
} }
@ -112,5 +121,43 @@ class MessageSvc {
return ForceOfflineEvent(bot, title = struct.title ?: "", tips = struct.tips ?: "") return ForceOfflineEvent(bot, title = struct.title ?: "", tips = struct.tips ?: "")
} }
} }
internal object PbSendMsg : PacketFactory<MsgSvc.PbSendMsgResp>("MessageSvc.PbSendMsg") {
object Response : Packet
/**
* 发送好友消息
*/
fun ToFriend(
client: QQAndroidClient,
toUin: Long,
message: MessageChain
): OutgoingPacket = buildOutgoingUniPacket(client) {
///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes())
///return@buildOutgoingUniPacket
writeProtoBuf(
MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq(
routingHead = MsgSvc.RoutingHead(c2c = MsgSvc.C2C(toUin = toUin)),
contentHead = MsgComm.ContentHead(pkgNum = 1),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = message.toRichTextElems()
)
),
msgSeq = 17041,
msgRand = Random.nextInt().absoluteValue,
syncCookie = client.c2cMessageSync.syncCookie.takeIf { it.isNotEmpty() } ?: "08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00".hexToBytes(),
msgVia = 1
)
)
}
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): MsgSvc.PbSendMsgResp {
discardExact(4)
return readRemainingAsProtoBuf(MsgSvc.PbSendMsgResp.serializer())
}
}
} }

View File

@ -5,10 +5,11 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes import kotlinx.io.core.readBytes
import kotlinx.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.message.GroupMessage import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgOnlinePush import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgOnlinePush
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
@ -24,7 +25,7 @@ internal class OnlinePush {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): GroupMessage { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): GroupMessage {
// 00 00 02 E4 0A D5 05 0A 4F 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 52 20 00 28 BC 3D 30 8C 82 AB F1 05 38 D2 80 E0 8C 80 80 80 80 02 4A 21 08 E7 C1 AD B8 02 10 01 18 BA 05 22 09 48 69 6D 31 38 38 6D 6F 65 30 06 38 02 42 05 4D 69 72 61 69 50 01 58 01 60 00 88 01 08 12 06 08 01 10 00 18 00 1A F9 04 0A F6 04 0A 26 08 00 10 87 82 AB F1 05 18 B7 B4 BF 30 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 E6 03 42 E3 03 12 2A 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 22 00 2A 04 03 00 00 00 32 60 15 36 20 39 36 6B 45 31 41 38 35 32 32 39 64 63 36 39 38 34 37 39 37 37 62 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 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 31 32 31 32 41 38 C6 BB 8A A9 08 40 FB AE 9E C2 09 48 50 50 41 5A 00 60 01 6A 10 4E 18 58 22 0E 7B F8 0F C5 B1 34 48 83 74 D3 9C 72 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 31 39 38 3F 74 65 72 6D 3D 32 82 01 57 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 30 3F 74 65 72 6D 3D 32 B0 01 4D B8 01 2E C8 01 FF 05 D8 01 4D E0 01 2E FA 01 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 34 30 30 3F 74 65 72 6D 3D 32 80 02 4D 88 02 2E 12 45 AA 02 42 50 03 60 00 68 00 9A 01 39 08 09 20 BF 50 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 04 08 02 08 01 90 04 80 80 80 10 B8 04 00 C0 04 00 12 06 4A 04 08 00 40 01 12 14 82 01 11 0A 09 48 69 6D 31 38 38 6D 6F 65 18 06 20 08 28 03 10 8A CA 9D A1 07 1A 00 // 00 00 02 E4 0A D5 05 0A 4F 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 52 20 00 28 BC 3D 30 8C 82 AB F1 05 38 D2 80 E0 8C 80 80 80 80 02 4A 21 08 E7 C1 AD B8 02 10 01 18 BA 05 22 09 48 69 6D 31 38 38 6D 6F 65 30 06 38 02 42 05 4D 69 72 61 69 50 01 58 01 60 00 88 01 08 12 06 08 01 10 00 18 00 1A F9 04 0A F6 04 0A 26 08 00 10 87 82 AB F1 05 18 B7 B4 BF 30 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 E6 03 42 E3 03 12 2A 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 22 00 2A 04 03 00 00 00 32 60 15 36 20 39 36 6B 45 31 41 38 35 32 32 39 64 63 36 39 38 34 37 39 37 37 62 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 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 31 32 31 32 41 38 C6 BB 8A A9 08 40 FB AE 9E C2 09 48 50 50 41 5A 00 60 01 6A 10 4E 18 58 22 0E 7B F8 0F C5 B1 34 48 83 74 D3 9C 72 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 31 39 38 3F 74 65 72 6D 3D 32 82 01 57 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 30 3F 74 65 72 6D 3D 32 B0 01 4D B8 01 2E C8 01 FF 05 D8 01 4D E0 01 2E FA 01 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 34 30 30 3F 74 65 72 6D 3D 32 80 02 4D 88 02 2E 12 45 AA 02 42 50 03 60 00 68 00 9A 01 39 08 09 20 BF 50 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 04 08 02 08 01 90 04 80 80 80 10 B8 04 00 C0 04 00 12 06 4A 04 08 00 40 01 12 14 82 01 11 0A 09 48 69 6D 31 38 38 6D 6F 65 18 06 20 08 28 03 10 8A CA 9D A1 07 1A 00
discardExact(4) discardExact(4)
val pbPushMsg = ProtoBuf.load(MsgOnlinePush.PbPushMsg.serializer(), readBytes()) val pbPushMsg = ProtoBufWithNullableSupport.load(MsgOnlinePush.PbPushMsg.serializer(), readBytes())
val extraInfo: ImMsgBody.ExtraInfo? = pbPushMsg.msg.msgBody.richText.elems.firstOrNull { it.extraInfo != null }?.extraInfo val extraInfo: ImMsgBody.ExtraInfo? = pbPushMsg.msg.msgBody.richText.elems.firstOrNull { it.extraInfo != null }?.extraInfo
@ -48,5 +49,11 @@ internal class OnlinePush {
} }
) )
} }
override suspend fun QQAndroidBot.handle(packet: GroupMessage) {
if (packet.senderName == "Him188moe") {
this.getQQ(2978594313).sendMessage("FROM MIRAI")
}
}
} }
} }

View File

@ -267,9 +267,7 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("wt
sealed class LoginPacketResponse : Packet { sealed class LoginPacketResponse : Packet {
object Success : LoginPacketResponse() { object Success : LoginPacketResponse() {
override fun toString(): String { override fun toString(): String = "LoginPacketResponse.Success"
return "LoginPacketResponse.Success"
}
} }
data class Error( data class Error(
@ -283,19 +281,23 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("wt
class Slider( class Slider(
val url: String val url: String
) : Captcha() { ) : Captcha() {
override fun toString(): String { override fun toString(): String = "LoginPacketResponse.Captcha.Slider"
return "LoginPacketResponse.Captcha.Slider"
}
} }
class Picture( class Picture(
val data: IoBuffer, val data: IoBuffer,
val sign: ByteArray val sign: ByteArray
) : Captcha() { ) : Captcha() {
override fun toString(): String { override fun toString(): String = "LoginPacketResponse.Captcha.Picture"
return "LoginPacketResponse.Captcha.Picture"
} }
} }
data class UnsafeLogin(val url: String) : LoginPacketResponse()
class SMSVerifyCodeNeeded(val t402: ByteArray, val t403: ByteArray) : LoginPacketResponse(){
override fun toString(): String {
return "LoginPacketResponse.SMSVerifyCodeNeeded"
}
} }
class UnsafeLogin(val url: String) : LoginPacketResponse() class UnsafeLogin(val url: String) : LoginPacketResponse()

View File

@ -1,7 +1,7 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.login package net.mamoe.mirai.qqandroid.network.protocol.packet.login
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.serialization.protobuf.ProtoBuf import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport
import net.mamoe.mirai.data.Packet import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
@ -103,7 +103,7 @@ class StatSvc {
var44.strVendorName = ROMUtil.getRomName(); var44.strVendorName = ROMUtil.getRomName();
var44.strVendorOSName = ROMUtil.getRomVersion(20); var44.strVendorOSName = ROMUtil.getRomVersion(20);
*/ */
bytes_0x769_reqbody = ProtoBuf.dump( bytes_0x769_reqbody = ProtoBufWithNullableSupport.dump(
Oidb0x769.RequestBody.serializer(), Oidb0x769.RequestBody( Oidb0x769.RequestBody.serializer(), Oidb0x769.RequestBody(
rpt_config_list = listOf( rpt_config_list = listOf(
Oidb0x769.ConfigSeq( Oidb0x769.ConfigSeq(

View File

@ -2,7 +2,7 @@ package net.mamoe.mirai.qqandroid.utils
import kotlinx.serialization.SerialId import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoBuf import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport
import net.mamoe.mirai.utils.cryptor.contentToString import net.mamoe.mirai.utils.cryptor.contentToString
import net.mamoe.mirai.utils.getValue import net.mamoe.mirai.utils.getValue
import net.mamoe.mirai.utils.unsafeWeakRef import net.mamoe.mirai.utils.unsafeWeakRef
@ -62,7 +62,7 @@ abstract class DeviceInfo(
@SerialId(9) val innerVersion: ByteArray @SerialId(9) val innerVersion: ByteArray
) )
return ProtoBuf.dump( return ProtoBufWithNullableSupport.dump(
DevInfo.serializer(), DevInfo( DevInfo.serializer(), DevInfo(
bootloader, bootloader,
procVersion, procVersion,

View File

@ -3,16 +3,15 @@ package net.mamoe.mirai.qqandroid.utils
import net.mamoe.mirai.data.ImageLink import net.mamoe.mirai.data.ImageLink
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
internal fun MessageChain.constructPbSendMsgReq(): MsgSvc.PbSendMsgReq { internal fun MessageChain.toRichTextElems(): MutableList<ImMsgBody.Elem> {
val request = MsgSvc.PbSendMsgReq() val elems = mutableListOf<ImMsgBody.Elem>()
this.forEach { this.forEach {
when (it) { when (it) {
is PlainText -> { is PlainText -> {
request.msgBody.richText.elems.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = it.stringValue))) elems.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = it.stringValue)))
} }
is At -> { is At -> {
@ -20,8 +19,7 @@ internal fun MessageChain.constructPbSendMsgReq(): MsgSvc.PbSendMsgReq {
} }
} }
return elems
return request
} }

View File

@ -7,7 +7,6 @@ import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.data.Packet import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.cryptor.encryptAndWrite import net.mamoe.mirai.utils.cryptor.encryptAndWrite
import net.mamoe.mirai.utils.io.hexToBytes import net.mamoe.mirai.utils.io.hexToBytes

View File

@ -200,9 +200,9 @@ fun Any?.contentToString(prefix: String = ""): String = when (this) {
} }
is ProtoMap -> "ProtoMap(size=$size){\n" + this.toStringPrefixed("$prefix${ProtoMap.indent}${ProtoMap.indent}") + "\n$prefix${ProtoMap.indent}}" is ProtoMap -> "ProtoMap(size=$size){\n" + this.toStringPrefixed("$prefix${ProtoMap.indent}${ProtoMap.indent}") + "\n$prefix${ProtoMap.indent}}"
is Iterable<*> -> this.joinToString(prefix = "[", postfix = "]") { it.contentToString() } is Iterable<*> -> this.joinToString(prefix = "[", postfix = "]") { it.contentToString(prefix) }
is Iterator<*> -> this.asSequence().joinToString(prefix = "[", postfix = "]") { it.contentToString() } is Iterator<*> -> this.asSequence().joinToString(prefix = "[", postfix = "]") { it.contentToString(prefix) }
is Sequence<*> -> this.joinToString(prefix = "[", postfix = "]") { it.contentToString() } is Sequence<*> -> this.joinToString(prefix = "[", postfix = "]") { it.contentToString(prefix) }
is Map<*, *> -> this.entries.joinToString(prefix = "{", postfix = "}") { it.key.contentToString(prefix) + "=" + it.value.contentToString(prefix) } is Map<*, *> -> this.entries.joinToString(prefix = "{", postfix = "}") { it.key.contentToString(prefix) + "=" + it.value.contentToString(prefix) }
else -> { else -> {
if (this == null) "null" if (this == null) "null"

View File

@ -1,23 +0,0 @@
package test
import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.read
import net.mamoe.mirai.utils.io.readTLVMap
import net.mamoe.mirai.utils.io.toUHexString
@ExperimentalStdlibApi
@Suppress("EXPERIMENTAL_API_USAGE")
fun main() {
val newMap =
"4E 22 00 03 E5 AE 89 4E 25 00 06 35 31 31 34 39 35 4E 26 00 01 2D 4E 27 00 01 2D 4E 29 00 01 02 4E 2A 00 06 56 69 76 69 61 6E 4E 2B 00 10 31 35 36 31 34 38 39 31 33 40 71 71 2E 63 6F 6D 4E 2D 00 1D 68 74 74 70 3A 2F 2F 31 35 36 31 34 38 39 31 33 2E 71 7A 6F 6E 65 2E 71 71 2E 63 6F 6D 4E 2E 00 02 33 00 4E 2F 00 04 33 33 39 00 4E 30 00 01 2D 4E 31 00 01 00 4E 33 00 2D E6 88 91 E7 95 99 E9 95 BF E7 9A 84 E5 A4 B4 E5 8F 91 EF BC 8C E6 98 AF E4 BD A0 E9 94 99 E8 BF 87 E7 9A 84 E5 B9 B4 E5 8D 8E 2E 2E 2E 4E 35 00 18 E5 B9 BF E4 B8 9C E6 8A 80 E6 9C AF E5 B8 88 E8 8C 83 E5 AD A6 E9 99 A2 4E 36 00 01 0A 4E 37 00 01 03 4E 38 00 01 01 4E 3F 00 04 07 C2 0B 02 4E 40 00 0C 00 00 00 31 00 00 34 34 00 00 00 33 4E 41 00 02 00 00 4E 42 00 02 00 00 4E 43 00 02 00 00 4E 45 00 01 21 4E 49 00 04 00 00 00 00 4E 4B 00 04 00 00 00 00 4E 4F 00 01 00 4E 54 00 00 4E 5B 00 00 52 0B 00 04 13 88 02 02 52 0F 00 14 00 00 00 00 00 00 00 00 12 05 10 58 89 10 00 00 00 00 00 00 5D C2 00 0C 00 00 00 31 00 00 34 34 00 00 31 34 5D C8 00 1E E7 B4 A2 E5 B0 BC EF BC 88 E4 B8 AD E5 9B BD EF BC 89 E6 9C 89 E9 99 90 E5 85 AC E5 8F B8 65 97 00 01 11 69 9D 00 04 00 00 00 00 69 A9 00 00 9D A5 00 02 00 00 A4 91 00 02 00 00 A4 93 00 02 00 00 A4 94 00 02 00 00 A4 9C 00 02 00 00 A4 B5 00 02 00 00"
.hexToBytes().read {
readTLVMap(tagSize = 2, expectingEOF = true)
}
newMap.forEach { (key, value) ->
if (!(value.isEmpty() || value.all { it.toInt() == 0 })) {
println(key.toUShort().toUHexString() + "=" + value.decodeToString())
}
}
return
}

View File

@ -1,55 +0,0 @@
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package test
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoBuf
import kotlinx.serialization.protobuf.ProtoNumberType
import kotlinx.serialization.protobuf.ProtoType
import kotlinx.serialization.serializer
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.cryptor.readProtoMap
import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.read
import kotlin.reflect.KClass
@Serializable
data class ProtoTest(
//@SerialId(1) val string: String,
//@SerialId(1) val int: Int,
//@SerialId(1) val boolean: Boolean,
//@SerialId(1) val short: Short,
//@SerialId(1) val byte: Byte,
@SerialId(1) @ProtoType(ProtoNumberType.FIXED) val fixedByte: Byte
)
@UseExperimental(MiraiInternalAPI::class)
suspend fun main() {
deserializeTest()
}
suspend fun deserializeTest() {
val bytes =
"""
08 02 1A 55 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 1A 25 2F 34 35 35 38 66 39 30 38 2D 37 62 39 61 2D 34 65 32 66 2D 38 63 36 39 2D 34 61 35 32 61 66 62 33 36 35 61 37 20 01 30 04 38 05 40 09 48 01 58 00 60 01 6A 0A 38 2E 32 2E 30 2E 31 32 39 36 70 E0 8C B2 F0 05 78 01 50 03
""".trimIndent()
.replace("\n", " ")
.replace("UVarInt", "", ignoreCase = true)
.replace("uint", "", ignoreCase = true)
.replace("[", "")
.replace("]", "")
.replace("(", "")
.replace(")", "")
.replace(" ", " ")
.replace(" ", " ")
.replace(" ", " ")
.replace("_", "")
.replace(",", "")
.hexToBytes()
println(bytes.read { readProtoMap() })
}
@UseExperimental(ImplicitReflectionSerializer::class)
fun <T : Any> KClass<T>.loadFrom(protoBuf: ByteArray): T = ProtoBuf.load(this.serializer(), protoBuf)