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/25
- 完成 图片验证码登录 (2020/1/26
- 进行中 免密登录
- 进行中 SMS 登录
- 完成 设备锁登录 (2020/1/29)
- 进行中 消息解析和发送
- 进行中 图片上传和下载

View File

@ -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<MessageSvc.PbSendMsg.Response>()
}
}
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<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 {
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")

View File

@ -33,7 +33,7 @@ internal abstract class QQAndroidBotBase constructor(
override val qqs: ContactList<QQ> = 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<Group> = 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 {

View File

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

View File

@ -6,6 +6,8 @@ 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
import net.mamoe.mirai.utils.io.toUHexString
/**
* 仅有标示作用
@ -13,26 +15,29 @@ import kotlinx.serialization.SerializationStrategy
interface ProtoBuf
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
*/
fun <T : ProtoBuf> T.toByteArray(serializer: SerializationStrategy<T>): ByteArray {
return kotlinx.serialization.protobuf.ProtoBuf.dump(serializer, this)
return ProtoBufWithNullableSupport.dump(serializer, this)
}
/**
* load
*/
fun <T : ProtoBuf> ByteArray.loadAs(deserializer: DeserializationStrategy<T>): T {
return kotlinx.serialization.protobuf.ProtoBuf.load(deserializer, this)
return ProtoBufWithNullableSupport.load(deserializer, this)
}
/**
* load
*/
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)
}
} 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)
}

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

View File

@ -469,7 +469,7 @@ class ImMsgBody : ProtoBuf {
@SerialId(16) val bubbleSubId: Int = 0,
@SerialId(17) val pendantId: Long = 0L,
@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
@Serializable
@ -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<ByteArray>? = null,
@SerialId(11) val bytesFileUrls: List<ByteArray>? = 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<ByteArray>? = null,
@SerialId(31) val bytesPttUrls: List<ByteArray>? = listOf(),
@SerialId(32) val downloadFlag: Int = 0
) : ProtoBuf
@ -813,8 +813,8 @@ class ImMsgBody : ProtoBuf {
class RichText(
@SerialId(1) val attr: Attr? = null,
@SerialId(2) val elems: MutableList<Elem> = mutableListOf(),
@SerialId(3) val notOnlineFile: NotOnlineFile? = null,
@SerialId(4) val ptt: Ptt? = null,
@SerialId(3) val notOnlineFile: NotOnlineFile? =null,
@SerialId(4) val ptt: Ptt? =null,
@SerialId(5) val tmpPtt: TmpPtt? = null,
@SerialId(6) val trans211TmpMsg: Trans211TmpMsg? = null
) : ProtoBuf
@ -1045,9 +1045,9 @@ class ImMsgHead : ProtoBuf {
@Serializable
class InstCtrl(
@SerialId(1) val msgSendToInst: List<InstInfo>? = null,
@SerialId(2) val msgExcludeInst: List<InstInfo>? = null,
@SerialId(3) val msgFromInst: InstInfo? = null
@SerialId(1) val msgSendToInst: List<InstInfo>? = listOf(),
@SerialId(2) val msgExcludeInst: List<InstInfo>? = 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

View File

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

View File

@ -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 : 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,
MessageSvc.PushNotify,
MessageSvc.PbGetMsg,
MessageSvc.PushForceOffline
MessageSvc.PushForceOffline,
MessageSvc.PbSendMsg
) {
// SvcReqMSFLoginNotify 自己的其他设备上限
// 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.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<ImageDownPacket.ImageDownPacketR
// TODO: 2020/1/24 测试: bodyType, subAppId
return buildLoginOutgoingPacket(client, key = client.wLoginSigInfo.d2Key, bodyType = 1) {
writeSsoPacket(client, subAppId = 0, commandName = commandName, sequenceId = it) {
val data = ProtoBuf.dump(
val data = ProtoBufWithNullableSupport.dump(
Cmd0x352Packet.serializer(),
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.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 ImageUpPacket : PacketFactory<ImageUpPacket.ImageUpPacketRespons
// TODO: 2020/1/24 测试: bodyType, subAppId
return buildLoginOutgoingPacket(client, key = client.wLoginSigInfo.d2Key, bodyType = 1) {
writeSsoPacket(client, subAppId = 0, commandName = ImageDownPacket.commandName, sequenceId = it) {
val data = ProtoBuf.dump(
val data = ProtoBufWithNullableSupport.dump(
Cmd0x352Packet.serializer(),
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.discardExact
import kotlinx.io.core.writeFully
import net.mamoe.mirai.data.MultiPacket
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.event.ForceOfflineEvent
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.protocol.data.jce.RequestPushForceOffline
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.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
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.io.hexToBytes
import net.mamoe.mirai.utils.io.toReadPacket
import kotlin.math.absoluteValue
import kotlin.random.Random
class MessageSvc {
/**
* 告知要刷新消息
* 告知要刷新好友消息
*/
internal object PushNotify : PacketFactory<RequestPushNotify>("MessageSvc.PushNotify") {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): RequestPushNotify {
@ -41,7 +49,7 @@ class MessageSvc {
/**
* 进行刷新消息
* 获取好友消息和消息记录
*/
internal object PbGetMsg : PacketFactory<MultiPacket<FriendMessage>>("MessageSvc.PbGetMsg") {
val EXTRA_DATA =
@ -65,9 +73,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 +121,43 @@ class MessageSvc {
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.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")
}
}
}
}

View File

@ -267,9 +267,7 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("wt
sealed class LoginPacketResponse : Packet {
object Success : LoginPacketResponse() {
override fun toString(): String {
return "LoginPacketResponse.Success"
}
override fun toString(): String = "LoginPacketResponse.Success"
}
data class Error(
@ -283,18 +281,22 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("wt
class Slider(
val url: String
) : Captcha() {
override fun toString(): String {
return "LoginPacketResponse.Captcha.Slider"
}
override fun toString(): String = "LoginPacketResponse.Captcha.Slider"
}
class Picture(
val data: IoBuffer,
val sign: ByteArray
) : Captcha() {
override fun toString(): String {
return "LoginPacketResponse.Captcha.Picture"
}
override fun toString(): String = "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"
}
}

View File

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

View File

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

View File

@ -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.toRichTextElems(): MutableList<ImMsgBody.Elem> {
val elems = mutableListOf<ImMsgBody.Elem>()
this.forEach {
when (it) {
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 -> {
@ -20,8 +19,7 @@ internal fun MessageChain.constructPbSendMsgReq(): MsgSvc.PbSendMsgReq {
}
}
return request
return elems
}

View File

@ -7,7 +7,6 @@ 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.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.cryptor.encryptAndWrite
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 Iterable<*> -> this.joinToString(prefix = "[", postfix = "]") { it.contentToString() }
is Iterator<*> -> this.asSequence().joinToString(prefix = "[", postfix = "]") { it.contentToString() }
is Sequence<*> -> this.joinToString(prefix = "[", postfix = "]") { it.contentToString() }
is Iterable<*> -> this.joinToString(prefix = "[", postfix = "]") { it.contentToString(prefix) }
is Iterator<*> -> this.asSequence().joinToString(prefix = "[", postfix = "]") { it.contentToString(prefix) }
is Sequence<*> -> this.joinToString(prefix = "[", postfix = "]") { it.contentToString(prefix) }
is Map<*, *> -> this.entries.joinToString(prefix = "{", postfix = "}") { it.key.contentToString(prefix) + "=" + it.value.contentToString(prefix) }
else -> {
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)