From 6b432b2aa632060c6a207b8d24cba66f62c10ef9 Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 30 Jan 2020 01:34:23 +0800 Subject: [PATCH] ProtoBufWithNullableSupport --- .../net/mamoe/mirai/qqandroid/ContactImpl.kt | 21 +- .../net/mamoe/mirai/qqandroid/QQAndroidBot.kt | 8 +- .../qqandroid/event/ForceOfflineEvent.kt | 2 +- .../net/mamoe/mirai/qqandroid/io/ProtoBuf.kt | 7 +- .../mirai/qqandroid/io/serialization/Jce.kt | 2 +- .../ProtoBufWithNullableSupport.kt | 483 ++++++++++++++++++ .../network/QQAndroidBotNetworkHandler.kt | 6 +- .../network/protocol/data/proto/Msg.kt | 24 +- .../network/protocol/data/proto/MsgSvc.kt | 7 +- .../network/protocol/packet/MessageMicro.kt | 4 +- .../network/protocol/packet/PacketFactory.kt | 3 +- .../packet/chat/image/ImageDownPacket.kt | 4 +- .../packet/chat/image/ImageUpPacket.kt | 4 +- .../packet/chat/receive/MessageSvc.kt | 51 +- .../chat/receive/OnlinePush.PbPushGroupMsg.kt | 11 +- .../network/protocol/packet/login/Register.kt | 4 +- .../mamoe/mirai/qqandroid/utils/DeviceInfo.kt | 4 +- .../mamoe/mirai/qqandroid/utils/MessageQQA.kt | 10 +- .../network/packet/OutgoingPacket.kt | 3 +- .../network/packet/PacketFactory.kt | 2 +- .../net.mamoe.mirai.timpc/utils/writeProto.kt | 2 +- mirai-debug/src/main/kotlin/test/ProtoTest.kt | 2 +- 22 files changed, 601 insertions(+), 63 deletions(-) create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/ProtoBufWithNullableSupport.kt diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt index 4d29779e6..e218225b1 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt @@ -1,6 +1,5 @@ package net.mamoe.mirai.qqandroid -import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.* import net.mamoe.mirai.data.FriendNameRemark 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.message.data.ImageId import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc import net.mamoe.mirai.utils.* import kotlin.coroutines.CoroutineContext internal abstract class ContactImpl : Contact -internal class QQImpl(bot: Bot, override val coroutineContext: CoroutineContext, override val id: Long) : ContactImpl(), QQ { - override val bot: Bot by bot.unsafeWeakRef() +internal class QQImpl(bot: QQAndroidBot, override val coroutineContext: CoroutineContext, override val id: Long) : ContactImpl(), QQ { + override val bot: QQAndroidBot by bot.unsafeWeakRef() override suspend fun sendMessage(message: MessageChain) { - TODO("not implemented") + bot.network.run { + MessageSvc.PbSendMsg.ToFriend(bot.client, id, message).sendAndExpect() + } } 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 permission: MemberPermission 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 { TODO("not implemented") @@ -76,7 +78,7 @@ internal class MemberImpl(bot: Bot, group: Group, override val coroutineContext: @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 owner: Member get() = TODO("not implemented") @@ -86,7 +88,8 @@ internal class GroupImpl(bot: Bot, override val coroutineContext: CoroutineConte get() = TODO("not implemented") override val members: ContactList = 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 { TODO("not implemented") @@ -96,7 +99,7 @@ internal class GroupImpl(bot: Bot, override val coroutineContext: CoroutineConte TODO("not implemented") } - override val bot: Bot by bot.unsafeWeakRef() + override val bot: QQAndroidBot by bot.unsafeWeakRef() override suspend fun sendMessage(message: MessageChain) { TODO("not implemented") diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt index 6c925ee99..5bf60d07e 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt @@ -33,7 +33,7 @@ internal abstract class QQAndroidBotBase constructor( override val qqs: ContactList = ContactList(LockFreeLinkedList()) 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 { @@ -43,17 +43,17 @@ internal abstract class QQAndroidBotBase constructor( override val groups: ContactList = ContactList(LockFreeLinkedList()) 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 { 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 { - 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 { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/event/ForceOfflineEvent.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/event/ForceOfflineEvent.kt index 381b170ef..5529e0b52 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/event/ForceOfflineEvent.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/event/ForceOfflineEvent.kt @@ -7,7 +7,7 @@ import net.mamoe.mirai.event.events.BotEvent /** * 被挤下线 */ -class ForceOfflineEvent( +data class ForceOfflineEvent( override val bot: Bot, val title: String, val tips: String diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/ProtoBuf.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/ProtoBuf.kt index e5ebf2298..baae9c9c3 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/ProtoBuf.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/ProtoBuf.kt @@ -6,6 +6,7 @@ import kotlinx.io.core.readBytes import kotlinx.io.core.writeFully import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerializationStrategy +import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport /** * 仅有标示作用 @@ -20,19 +21,19 @@ fun BytePacketBuilder.writeProtoBuf(serializer: SerializationStra * dump */ fun T.toByteArray(serializer: SerializationStrategy): ByteArray { - return kotlinx.serialization.protobuf.ProtoBuf.dump(serializer, this) + return ProtoBufWithNullableSupport.dump(serializer, this) } /** * load */ fun ByteArray.loadAs(deserializer: DeserializationStrategy): T { - return kotlinx.serialization.protobuf.ProtoBuf.load(deserializer, this) + return ProtoBufWithNullableSupport.load(deserializer, this) } /** * load */ fun Input.readRemainingAsProtoBuf(serializer: DeserializationStrategy): T { - return kotlinx.serialization.protobuf.ProtoBuf.load(serializer, this.readBytes()) + return ProtoBufWithNullableSupport.load(serializer, this.readBytes()) } \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt index 6fbe5a28e..a8d455454 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt @@ -191,7 +191,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo this.writeHead(STRUCT_END, 0) } } 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 { serializer.serialize(this, value) } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/ProtoBufWithNullableSupport.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/ProtoBufWithNullableSupport.kt new file mode 100644 index 000000000..1c06ac7f0 --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/ProtoBufWithNullableSupport.kt @@ -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 + +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(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() { + 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 encodeNullableSerializableValue(serializer: SerializationStrategy, value: T?) { + if (value == null) { + encodeTaggedNull(popTag()) + } else encodeSerializableValue(serializer, value) + } + // MIRAI MODIFY END + + @Suppress("UNCHECKED_CAST", "NAME_SHADOWING") + override fun encodeSerializableValue(serializer: SerializationStrategy, 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) + 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() { + override val context: SerialModule + get() = this@ProtoBufWithNullableSupport.context + + private val indexByTag: MutableMap = 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 decodeSerializableValue(deserializer: DeserializationStrategy): T = when { + // encode maps as collection of map entries, not merged collection of key-values + deserializer.descriptor is MapLikeDescriptor -> { + val serializer = (deserializer as MapLikeSerializer) + 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 = -1 to -1 + + init { + readTag() + } + + private fun readTag(): Pair { + 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 dump(serializer: SerializationStrategy, obj: T): ByteArray = plain.dump(serializer, obj) + override fun load(deserializer: DeserializationStrategy, bytes: ByteArray): T = plain.load(deserializer, bytes) + override fun install(module: SerialModule) = throw IllegalStateException("You should not install anything to global instance") + } + + override fun dump(serializer: SerializationStrategy, obj: T): ByteArray { + val encoder = ByteArrayOutputStream() + val dumper = ProtobufWriter(ProtobufEncoder(encoder)) + dumper.encode(serializer, obj) + return encoder.toByteArray() + } + + override fun load(deserializer: DeserializationStrategy, 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 +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt index e9a26dadc..30dd995c3 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt @@ -318,10 +318,14 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler suspend fun OutgoingPacket.sendAndExpect(): E { val handler = PacketListener(commandName = commandName, sequenceId = sequenceId) packetListeners.addLast(handler) + bot.logger.info("Send: ${this.commandName}") channel.send(delegate) - return withTimeout(3000) { + return withTimeoutOrNull(3000) { @Suppress("UNCHECKED_CAST") handler.await() as E + } ?: net.mamoe.mirai.qqandroid.utils.inline { + packetListeners.remove(handler) + error("timeout when sending ${this.commandName}") } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Msg.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Msg.kt index ea1c3d3fc..cefbc44b9 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Msg.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Msg.kt @@ -609,7 +609,7 @@ class ImMsgBody : ProtoBuf { @SerialId(8) val reserved: Int = 0, @SerialId(9) val subcmd: Int = 0, @SerialId(10) val microCloud: Int = 0, - @SerialId(11) val bytesFileUrls: List? = null, + @SerialId(11) val bytesFileUrls: List? = listOf(), @SerialId(12) val downloadFlag: Int = 0, @SerialId(50) val dangerEvel: Int = 0, @SerialId(51) val lifeTime: Int = 0, @@ -705,7 +705,7 @@ class ImMsgBody : ProtoBuf { @SerialId(20) val downPara: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(29) val format: Int = 0, @SerialId(30) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(31) val bytesPttUrls: List? = null, + @SerialId(31) val bytesPttUrls: List? = listOf(), @SerialId(32) val downloadFlag: Int = 0 ) : ProtoBuf @@ -811,12 +811,12 @@ class ImMsgBody : ProtoBuf { @Serializable class RichText( - @SerialId(1) val attr: Attr? = null, + @SerialId(1) val attr: Attr? = Attr(), @SerialId(2) val elems: MutableList = mutableListOf(), - @SerialId(3) val notOnlineFile: NotOnlineFile? = null, - @SerialId(4) val ptt: Ptt? = null, - @SerialId(5) val tmpPtt: TmpPtt? = null, - @SerialId(6) val trans211TmpMsg: Trans211TmpMsg? = null + @SerialId(3) val notOnlineFile: NotOnlineFile? = NotOnlineFile(), + @SerialId(4) val ptt: Ptt? = Ptt(), + @SerialId(5) val tmpPtt: TmpPtt? = TmpPtt(), + @SerialId(6) val trans211TmpMsg: Trans211TmpMsg? = Trans211TmpMsg() ) : ProtoBuf @Serializable @@ -1045,9 +1045,9 @@ class ImMsgHead : ProtoBuf { @Serializable class InstCtrl( - @SerialId(1) val msgSendToInst: List? = null, - @SerialId(2) val msgExcludeInst: List? = null, - @SerialId(3) val msgFromInst: InstInfo? = null + @SerialId(1) val msgSendToInst: List? = listOf(), + @SerialId(2) val msgExcludeInst: List? = listOf(), + @SerialId(3) val msgFromInst: InstInfo? = InstInfo() ) : ProtoBuf @Serializable @@ -1107,7 +1107,7 @@ class ImReceipt : ProtoBuf { ) : ProtoBuf @Serializable - class ReceiptInfo( + data class ReceiptInfo( @SerialId(1) val readTime: Long = 0L ) : ProtoBuf @@ -1118,7 +1118,7 @@ class ImReceipt : ProtoBuf { ) : ProtoBuf @Serializable - class ReceiptResp( + data class ReceiptResp( @SerialId(1) val command: Int /* enum */ = 1, @SerialId(2) val receiptInfo: ReceiptInfo? = null ) : ProtoBuf diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgSvc.kt index c55d91ac0..8c1173eae 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgSvc.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgSvc.kt @@ -2,6 +2,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.proto import kotlinx.serialization.SerialId import kotlinx.serialization.Serializable +import net.mamoe.mirai.data.Packet import net.mamoe.mirai.qqandroid.io.ProtoBuf import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY @@ -102,7 +103,7 @@ class MsgSvc : ProtoBuf { ) : ProtoBuf @Serializable - class MsgSendInfo( + data class MsgSendInfo( @SerialId(1) val receiver: Int = 0 ) : ProtoBuf @@ -440,7 +441,7 @@ class MsgSvc : ProtoBuf { ) : ProtoBuf @Serializable - class PbSendMsgResp( + data class PbSendMsgResp( @SerialId(1) val result: Int = 0, @SerialId(2) val errmsg: String = "", @SerialId(3) val sendTime: Int = 0, @@ -450,7 +451,7 @@ class MsgSvc : ProtoBuf { @SerialId(7) val transSvrInfo: TransSvrInfo? = null, @SerialId(8) val receiptResp: ImReceipt.ReceiptResp? = null, @SerialId(9) val textAnalysisResult: Int = 0 - ) : ProtoBuf + ) : ProtoBuf, Packet @Serializable class PbBindUinUnReadMsgNumResp( diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/MessageMicro.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/MessageMicro.kt index 2c7d385d9..097d2d991 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/MessageMicro.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/MessageMicro.kt @@ -1,9 +1,9 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet import kotlinx.serialization.SerializationStrategy -import kotlinx.serialization.protobuf.ProtoBuf +import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport interface MessageMicro -fun T.toByteArray(serializer: SerializationStrategy): ByteArray = ProtoBuf.dump(serializer, this) \ No newline at end of file +fun T.toByteArray(serializer: SerializationStrategy): ByteArray = ProtoBufWithNullableSupport.dump(serializer, this) \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt index dcddea3a4..8c8a6d77d 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt @@ -62,7 +62,8 @@ internal object KnownPacketFactories : List> by mutableListOf( OnlinePush.PbPushGroupMsg, MessageSvc.PushNotify, MessageSvc.PbGetMsg, - MessageSvc.PushForceOffline + MessageSvc.PushForceOffline, + MessageSvc.PbSendMsg ) { // SvcReqMSFLoginNotify 自己的其他设备上限 // MessageSvc.PushReaded 电脑阅读了别人的消息, 告知手机 diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImageDownPacket.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImageDownPacket.kt index 52cbc6821..f6e7f6ded 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImageDownPacket.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImageDownPacket.kt @@ -2,7 +2,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image import kotlinx.io.core.ByteReadPacket 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.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.network.QQAndroidClient @@ -19,7 +19,7 @@ internal object ImageDownPacket : PacketFactory("MessageSvc.PushNotify") { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): RequestPushNotify { @@ -41,7 +47,7 @@ class MessageSvc { /** - * 进行刷新消息 + * 获取好友消息和消息记录 */ internal object PbGetMsg : PacketFactory>("MessageSvc.PbGetMsg") { val EXTRA_DATA = @@ -65,9 +71,10 @@ class MessageSvc { onlineSyncFlag = 1, // serverBuf = from.serverBuf ?: EMPTY_BYTE_ARRAY, syncCookie = client.c2cMessageSync.syncCookie, - syncFlag = client.c2cMessageSync.syncFlag, - msgCtrlBuf = client.c2cMessageSync.msgCtrlBuf, - pubaccountCookie = client.c2cMessageSync.pubAccountCookie + syncFlag = 1 + // syncFlag = client.c2cMessageSync.syncFlag, + //msgCtrlBuf = client.c2cMessageSync.msgCtrlBuf, + //pubaccountCookie = client.c2cMessageSync.pubAccountCookie ) ) } @@ -112,5 +119,39 @@ class MessageSvc { return ForceOfflineEvent(bot, title = struct.title ?: "", tips = struct.tips ?: "") } } + + internal object PbSendMsg : PacketFactory("MessageSvc.PbSendMsg") { + object Response : Packet + + /** + * 发送好友消息 + */ + fun ToFriend( + client: QQAndroidClient, + toUin: Long, + message: MessageChain + ): OutgoingPacket = buildOutgoingUniPacket(client) { + writeProtoBuf( + MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq( + routingHead = MsgSvc.RoutingHead(c2c = MsgSvc.C2C(toUin = toUin)), + contentHead = MsgComm.ContentHead(pkgNum = 1), + msgBody = ImMsgBody.MsgBody( + richText = message.toRichText().apply { + elems.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = "78 00 F8 01 00 C8 02 00".hexToBytes()))) + } + ), + msgSeq = 15741, + msgRand = Random.nextInt(), + syncCookie = client.c2cMessageSync.syncCookie, + msgVia = 1 + ) + ) + } + + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): MsgSvc.PbSendMsgResp { + discardExact(4) + return readRemainingAsProtoBuf(MsgSvc.PbSendMsgResp.serializer()) + } + } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.PbPushGroupMsg.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.PbPushGroupMsg.kt index 3f7919088..6e1e32862 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.PbPushGroupMsg.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.PbPushGroupMsg.kt @@ -5,10 +5,11 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.discardExact import kotlinx.io.core.readBytes -import kotlinx.serialization.protobuf.ProtoBuf import net.mamoe.mirai.contact.MemberPermission +import net.mamoe.mirai.contact.sendMessage import net.mamoe.mirai.message.GroupMessage 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.MsgOnlinePush import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory @@ -24,7 +25,7 @@ internal class OnlinePush { 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 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 @@ -48,5 +49,11 @@ internal class OnlinePush { } ) } + + override suspend fun QQAndroidBot.handle(packet: GroupMessage) { + if (packet.senderName == "Him188moe") { + this.getQQ(2978594313).sendMessage("FROM MIRAI") + } + } } } \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/Register.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/Register.kt index c02b07741..c7c10267c 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/Register.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/Register.kt @@ -1,7 +1,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.login 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.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.io.serialization.toByteArray @@ -103,7 +103,7 @@ class StatSvc { var44.strVendorName = ROMUtil.getRomName(); var44.strVendorOSName = ROMUtil.getRomVersion(20); */ - bytes_0x769_reqbody = ProtoBuf.dump( + bytes_0x769_reqbody = ProtoBufWithNullableSupport.dump( Oidb0x769.RequestBody.serializer(), Oidb0x769.RequestBody( rpt_config_list = listOf( Oidb0x769.ConfigSeq( diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/DeviceInfo.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/DeviceInfo.kt index 327a775a8..ade08022b 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/DeviceInfo.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/DeviceInfo.kt @@ -2,7 +2,7 @@ package net.mamoe.mirai.qqandroid.utils import kotlinx.serialization.SerialId 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.getValue import net.mamoe.mirai.utils.unsafeWeakRef @@ -62,7 +62,7 @@ abstract class DeviceInfo( @SerialId(9) val innerVersion: ByteArray ) - return ProtoBuf.dump( + return ProtoBufWithNullableSupport.dump( DevInfo.serializer(), DevInfo( bootloader, procVersion, diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/MessageQQA.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/MessageQQA.kt index 541f0d47a..7da313928 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/MessageQQA.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/MessageQQA.kt @@ -3,16 +3,15 @@ package net.mamoe.mirai.qqandroid.utils import net.mamoe.mirai.data.ImageLink 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.MsgSvc -internal fun MessageChain.constructPbSendMsgReq(): MsgSvc.PbSendMsgReq { - val request = MsgSvc.PbSendMsgReq() +internal fun MessageChain.toRichText(): ImMsgBody.RichText { + val richText = ImMsgBody.RichText() this.forEach { when (it) { is PlainText -> { - request.msgBody.richText.elems.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = it.stringValue))) + richText.elems.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = it.stringValue))) } is At -> { @@ -20,8 +19,7 @@ internal fun MessageChain.constructPbSendMsgReq(): MsgSvc.PbSendMsgReq { } } - - return request + return richText } diff --git a/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/OutgoingPacket.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/OutgoingPacket.kt index b4d9d3fef..13e4d51df 100644 --- a/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/OutgoingPacket.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/OutgoingPacket.kt @@ -4,10 +4,9 @@ package net.mamoe.mirai.timpc.network.packet import kotlinx.io.core.* import kotlinx.serialization.SerializationStrategy -import kotlinx.serialization.protobuf.ProtoBuf import net.mamoe.mirai.data.Packet import net.mamoe.mirai.network.BotNetworkHandler - +import net.mamoe.mirai.qqandroid.io.serialization.ProtoBuf import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.cryptor.encryptAndWrite import net.mamoe.mirai.utils.io.hexToBytes diff --git a/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/PacketFactory.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/PacketFactory.kt index 29cde3a08..59934b5f4 100644 --- a/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/PacketFactory.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/PacketFactory.kt @@ -9,9 +9,9 @@ import kotlinx.io.core.discardExact import kotlinx.io.core.readBytes import kotlinx.io.pool.useInstance import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.protobuf.ProtoBuf import net.mamoe.mirai.data.Packet import net.mamoe.mirai.network.BotNetworkHandler +import net.mamoe.mirai.qqandroid.io.serialization.ProtoBuf import net.mamoe.mirai.utils.cryptor.Decrypter import net.mamoe.mirai.utils.cryptor.DecrypterType import net.mamoe.mirai.utils.cryptor.readProtoMap diff --git a/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/utils/writeProto.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/utils/writeProto.kt index fa67ffd0f..1666dd9f6 100644 --- a/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/utils/writeProto.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/utils/writeProto.kt @@ -3,6 +3,6 @@ package net.mamoe.mirai.timpc.utils import kotlinx.io.core.BytePacketBuilder import kotlinx.io.core.writeFully import kotlinx.serialization.SerializationStrategy -import kotlinx.serialization.protobuf.ProtoBuf +import net.mamoe.mirai.qqandroid.io.serialization.ProtoBuf fun BytePacketBuilder.writeProto(serializer: SerializationStrategy, obj: T) = writeFully(ProtoBuf.dump(serializer, obj)) diff --git a/mirai-debug/src/main/kotlin/test/ProtoTest.kt b/mirai-debug/src/main/kotlin/test/ProtoTest.kt index 47f8b367e..ed7f3f4d5 100644 --- a/mirai-debug/src/main/kotlin/test/ProtoTest.kt +++ b/mirai-debug/src/main/kotlin/test/ProtoTest.kt @@ -5,10 +5,10 @@ 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.qqandroid.io.serialization.ProtoBuf import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.cryptor.readProtoMap import net.mamoe.mirai.utils.io.hexToBytes