Commit 06515bc0 authored by jiahua.liu's avatar jiahua.liu

Merge remote-tracking branch 'origin/master'

# Conflicts:
#	mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
#	mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/list/FriendListPacket.kt
parents 0b01fd7b b85c91b2
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
[![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/) [![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/)
**[English](README-eng.md)** **[English](README-eng.md)**
平台 **TIM PC 和 QQ Android** 协议支持库. 平台 **TIM PC 和 QQ Android** 协议支持库.
纯 Kotlin 实现协议和支持框架,模块全部开源。 纯 Kotlin 实现协议和支持框架,模块全部开源。
目前可运行在 JVM 或 Android。 目前可运行在 JVM 或 Android。
...@@ -13,16 +13,16 @@ ...@@ -13,16 +13,16 @@
加入 Gitter, 或加入 QQ 群: 655057127 加入 Gitter, 或加入 QQ 群: 655057127
## Update log ## CHANGELOG
[Project](https://github.com/mamoe/mirai/projects/1) 查看已支持功能和计划(更新不及时) [Project](https://github.com/mamoe/mirai/projects/1) 查看已支持功能和计划(更新不及时)
[UpdateLog](https://github.com/mamoe/mirai/blob/master/UpdateLog.md) 查看版本更新记录(准确更新发布的版本) [CHANGELOG](https://github.com/mamoe/mirai/blob/master/CHANGELOG.md) 查看版本更新记录(准确更新发布的版本)
## Modules ## Modules
#### mirai-core ### mirai-core
通用 API 模块,一套 API 适配两套协议。 通用 API 模块,一套 API 适配两套协议。
**请参考此模块的 API** **请参考此模块的 API**
#### mirai-core-timpc ### mirai-core-timpc
TIM PC (2.3.2 版本,2019 年 8 月)协议的实现,相较于 core,仅新增少量 API. 详见 [README.md](mirai-core-timpc/) TIM PC (2.3.2 版本,2019 年 8 月)协议的实现,相较于 core,仅新增少量 API. 详见 [README.md](mirai-core-timpc/)
支持的功能: 支持的功能:
- 消息收发:图片文字复合消息,图片消息 - 消息收发:图片文字复合消息,图片消息
...@@ -30,17 +30,19 @@ TIM PC (2.3.2 版本,2019 年 8 月)协议的实现,相较于 core,仅 ...@@ -30,17 +30,19 @@ TIM PC (2.3.2 版本,2019 年 8 月)协议的实现,相较于 core,仅
(目前不再更新此协议,请关注下文的安卓协议) (目前不再更新此协议,请关注下文的安卓协议)
#### mirai-core-qqandroid ### mirai-core-qqandroid
QQ for Android (8.2.0 版本,2019 年 12 月)协议的实现,目前还未完成。 QQ for Android (8.2.0 版本,2019 年 12 月)协议的实现,目前还未完成。
- 高兼容性:Mirai 协议仅含极少部分为硬编码,其余全部随官方方式动态生成 - 高兼容性:协议仅含极少部分为硬编码,其余全部随官方方式动态生成
- 高安全性:密匙随机,ECDH 动态计算,硬件信息真机模拟(Android 平台获取真机信息) - 高安全性:密匙随机,ECDH 动态计算
开发进度: 开发进度:
- 完成 密码登录 (2020/1/23) - 完成 密码登录 (2020/1/23)
- 完成 群消息解析 (2020/1/25) - 完成 群消息解析 (2020/1/25)
- 完成 图片验证码登录 (2020/1/26) - 完成 图片验证码登录 (2020/1/26)
- 完成 设备锁登录 (2020/1/29) - 完成 "不安全"状态登录, 设备锁登录 (2020/1/29)
- 进行中 消息解析和发送 - 完成 群消息解析: 图片, 文字 (2020/1/31)
- 进行中 好友消息同步
- 进行中 好友列表, 群列表, 分组列表
- 进行中 图片上传和下载 - 进行中 图片上传和下载
## Use directly ## Use directly
......
# io.serialization
**序列化支持**
包含:
- QQ 的 JceStruct 相关的全自动序列化和反序列化: [Jce.kt](Jce.kt)
- Protocol Buffers 的 optional 支持: [ProtoBufWithNullableSupport.kt](ProtoBufWithNullableSupport.kt)
其中, ProtoBufWithNullableSupport.kt 的绝大部分源码来自 `kotlinx.serialization`. 原著权归该项目作者所有.
Mirai 所做的修改已经标记上了 `MIRAI MODIFY START`
\ No newline at end of file
...@@ -11,7 +11,6 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion3 ...@@ -11,7 +11,6 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion3
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
import net.mamoe.mirai.utils.firstValue import net.mamoe.mirai.utils.firstValue
import net.mamoe.mirai.utils.io.read import net.mamoe.mirai.utils.io.read
import net.mamoe.mirai.utils.io.toUHexString
fun <T : JceStruct> ByteArray.loadAs(deserializer: DeserializationStrategy<T>, c: JceCharset = JceCharset.UTF8): T { fun <T : JceStruct> ByteArray.loadAs(deserializer: DeserializationStrategy<T>, c: JceCharset = JceCharset.UTF8): T {
...@@ -22,7 +21,7 @@ fun <T : JceStruct> BytePacketBuilder.writeJceStruct(serializer: SerializationSt ...@@ -22,7 +21,7 @@ fun <T : JceStruct> BytePacketBuilder.writeJceStruct(serializer: SerializationSt
this.writePacket(Jce.byCharSet(charset).dumpAsPacket(serializer, struct)) this.writePacket(Jce.byCharSet(charset).dumpAsPacket(serializer, struct))
} }
fun <T : JceStruct> ByteReadPacket.readRemainingAsJceStruct( fun <T : JceStruct> ByteReadPacket.readJceStruct(
serializer: DeserializationStrategy<T>, serializer: DeserializationStrategy<T>,
charset: JceCharset = JceCharset.UTF8, charset: JceCharset = JceCharset.UTF8,
length: Int = this.remaining.toInt() length: Int = this.remaining.toInt()
...@@ -37,7 +36,7 @@ fun <T : JceStruct> ByteReadPacket.decodeUniPacket(deserializer: Deserialization ...@@ -37,7 +36,7 @@ fun <T : JceStruct> ByteReadPacket.decodeUniPacket(deserializer: Deserialization
return decodeUniRequestPacketAndDeserialize(name) { return decodeUniRequestPacketAndDeserialize(name) {
it.read { it.read {
discardExact(1) discardExact(1)
this.readRemainingAsJceStruct(deserializer, length = (this.remaining - 1).toInt()) this.readJceStruct(deserializer, length = (this.remaining - 1).toInt())
} }
} }
} }
...@@ -49,13 +48,13 @@ fun <T : ProtoBuf> ByteReadPacket.decodeUniPacket(deserializer: DeserializationS ...@@ -49,13 +48,13 @@ fun <T : ProtoBuf> ByteReadPacket.decodeUniPacket(deserializer: DeserializationS
return decodeUniRequestPacketAndDeserialize(name) { return decodeUniRequestPacketAndDeserialize(name) {
it.read { it.read {
discardExact(1) discardExact(1)
this.readRemainingAsProtoBuf(deserializer, (this.remaining - 1).toInt()) this.readProtoBuf(deserializer, (this.remaining - 1).toInt())
} }
} }
} }
fun <R> ByteReadPacket.decodeUniRequestPacketAndDeserialize(name: String? = null, block: (ByteArray) -> R): R { fun <R> ByteReadPacket.decodeUniRequestPacketAndDeserialize(name: String? = null, block: (ByteArray) -> R): R {
val request = this.readRemainingAsJceStruct(RequestPacket.serializer()) val request = this.readJceStruct(RequestPacket.serializer())
return block(if (name == null) when (request.iVersion.toInt()) { return block(if (name == null) when (request.iVersion.toInt()) {
2 -> request.sBuffer.loadAs(RequestDataVersion2.serializer()).map.firstValue().firstValue() 2 -> request.sBuffer.loadAs(RequestDataVersion2.serializer()).map.firstValue().firstValue()
...@@ -71,9 +70,7 @@ fun <R> ByteReadPacket.decodeUniRequestPacketAndDeserialize(name: String? = null ...@@ -71,9 +70,7 @@ fun <R> ByteReadPacket.decodeUniRequestPacketAndDeserialize(name: String? = null
fun <T : JceStruct> T.toByteArray(serializer: SerializationStrategy<T>, c: JceCharset = JceCharset.GBK): ByteArray = Jce.byCharSet(c).dump(serializer, this) fun <T : JceStruct> T.toByteArray(serializer: SerializationStrategy<T>, c: JceCharset = JceCharset.GBK): ByteArray = Jce.byCharSet(c).dump(serializer, this)
fun <T : ProtoBuf> BytePacketBuilder.writeProtoBuf(serializer: SerializationStrategy<T>, v: T) { fun <T : ProtoBuf> BytePacketBuilder.writeProtoBuf(serializer: SerializationStrategy<T>, v: T) {
this.writeFully(v.toByteArray(serializer).also { this.writeFully(v.toByteArray(serializer))
println("发送 protobuf: ${it.toUHexString()}")
})
} }
/** /**
...@@ -93,7 +90,7 @@ fun <T : ProtoBuf> ByteArray.loadAs(deserializer: DeserializationStrategy<T>): T ...@@ -93,7 +90,7 @@ fun <T : ProtoBuf> ByteArray.loadAs(deserializer: DeserializationStrategy<T>): T
/** /**
* load * load
*/ */
fun <T : ProtoBuf> ByteReadPacket.readRemainingAsProtoBuf(serializer: DeserializationStrategy<T>, length: Int = this.remaining.toInt()): T { fun <T : ProtoBuf> ByteReadPacket.readProtoBuf(serializer: DeserializationStrategy<T>, length: Int = this.remaining.toInt()): T {
return ProtoBufWithNullableSupport.load(serializer, this.readBytes(length)) return ProtoBufWithNullableSupport.load(serializer, this.readBytes(length))
} }
......
...@@ -11,15 +11,15 @@ import net.mamoe.mirai.qqandroid.QQAndroidBot ...@@ -11,15 +11,15 @@ import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger
import net.mamoe.mirai.qqandroid.network.protocol.packet.Tlv import net.mamoe.mirai.qqandroid.network.protocol.packet.Tlv
import net.mamoe.mirai.qqandroid.utils.* import net.mamoe.mirai.qqandroid.utils.Context
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.qqandroid.utils.DeviceInfo
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.qqandroid.utils.NetworkType
import net.mamoe.mirai.qqandroid.utils.SystemDeviceInfo
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.cryptor.ECDH import net.mamoe.mirai.utils.cryptor.ECDH
import net.mamoe.mirai.utils.cryptor.contentToString import net.mamoe.mirai.utils.cryptor.contentToString
import net.mamoe.mirai.utils.cryptor.decryptBy import net.mamoe.mirai.utils.cryptor.decryptBy
import net.mamoe.mirai.utils.getValue
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.unsafeWeakRef
/* /*
APP ID: APP ID:
...@@ -111,7 +111,7 @@ internal open class QQAndroidClient( ...@@ -111,7 +111,7 @@ internal open class QQAndroidClient(
val protocolVersion: Short = 8001 val protocolVersion: Short = 8001
class C2cMessageSyncData { class C2cMessageSyncData {
var syncCookie = EMPTY_BYTE_ARRAY var syncCookie: ByteArray? = null
var pubAccountCookie = EMPTY_BYTE_ARRAY var pubAccountCookie = EMPTY_BYTE_ARRAY
var syncFlag: Int = 0 var syncFlag: Int = 0
var msgCtrlBuf: ByteArray = EMPTY_BYTE_ARRAY var msgCtrlBuf: ByteArray = EMPTY_BYTE_ARRAY
...@@ -174,7 +174,11 @@ internal open class QQAndroidClient( ...@@ -174,7 +174,11 @@ internal open class QQAndroidClient(
lateinit var t104: ByteArray lateinit var t104: ByteArray
} }
class ReserveUinInfo( internal fun generateTgtgtKey(guid: ByteArray): ByteArray =
md5(getRandomByteArray(16) + guid)
internal class ReserveUinInfo(
val imgType: ByteArray, val imgType: ByteArray,
val imgFormat: ByteArray, val imgFormat: ByteArray,
val imgUrl: ByteArray val imgUrl: ByteArray
...@@ -184,7 +188,7 @@ class ReserveUinInfo( ...@@ -184,7 +188,7 @@ class ReserveUinInfo(
} }
} }
class WFastLoginInfo( internal class WFastLoginInfo(
val outA1: ByteReadPacket, val outA1: ByteReadPacket,
var adUrl: String = "", var adUrl: String = "",
var iconUrl: String = "", var iconUrl: String = "",
...@@ -196,7 +200,7 @@ class WFastLoginInfo( ...@@ -196,7 +200,7 @@ class WFastLoginInfo(
} }
} }
class WLoginSimpleInfo( internal class WLoginSimpleInfo(
val uin: Long, // uin val uin: Long, // uin
val face: Int, // ubyte actually val face: Int, // ubyte actually
val age: Int, // ubyte val age: Int, // ubyte
...@@ -212,7 +216,7 @@ class WLoginSimpleInfo( ...@@ -212,7 +216,7 @@ class WLoginSimpleInfo(
} }
} }
class LoginExtraData( internal class LoginExtraData(
val uin: Long, val uin: Long,
val ip: ByteArray, val ip: ByteArray,
val time: Int, val time: Int,
...@@ -223,7 +227,7 @@ class LoginExtraData( ...@@ -223,7 +227,7 @@ class LoginExtraData(
} }
} }
class WLoginSigInfo( internal class WLoginSigInfo(
val uin: Long, val uin: Long,
val encryptA1: ByteArray?, // sigInfo[0] val encryptA1: ByteArray?, // sigInfo[0]
val noPicSig: ByteArray?, // sigInfo[1] val noPicSig: ByteArray?, // sigInfo[1]
...@@ -275,24 +279,24 @@ class WLoginSigInfo( ...@@ -275,24 +279,24 @@ class WLoginSigInfo(
} }
} }
class UserStSig(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) internal class UserStSig(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
class LSKey(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) internal class LSKey(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime)
class UserStWebSig(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) internal class UserStWebSig(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime)
class UserA8(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) internal class UserA8(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime)
class UserA5(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) internal class UserA5(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
class SKey(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) internal class SKey(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime)
class UserSig64(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) internal class UserSig64(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
class OpenKey(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) internal class OpenKey(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
class VKey(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) internal class VKey(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime)
class AccessToken(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) internal class AccessToken(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
class D2(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) internal class D2(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime)
class Sid(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) internal class Sid(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime)
class AqSig(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) internal class AqSig(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
class Pt4Token(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) internal class Pt4Token(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime)
typealias PSKeyMap = MutableMap<String, PSKey> internal typealias PSKeyMap = MutableMap<String, PSKey>
typealias Pt4TokenMap = MutableMap<String, Pt4Token> internal typealias Pt4TokenMap = MutableMap<String, Pt4Token>
internal fun parsePSKeyMapAndPt4TokenMap(data: ByteArray, creationTime: Long, expireTime: Long, outPSKeyMap: PSKeyMap, outPt4TokenMap: Pt4TokenMap) = internal fun parsePSKeyMapAndPt4TokenMap(data: ByteArray, creationTime: Long, expireTime: Long, outPSKeyMap: PSKeyMap, outPt4TokenMap: Pt4TokenMap) =
data.read { data.read {
...@@ -308,17 +312,17 @@ internal fun parsePSKeyMapAndPt4TokenMap(data: ByteArray, creationTime: Long, ex ...@@ -308,17 +312,17 @@ internal fun parsePSKeyMapAndPt4TokenMap(data: ByteArray, creationTime: Long, ex
} }
} }
class PSKey(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime) internal class PSKey(data: ByteArray, creationTime: Long, expireTime: Long) : KeyWithExpiry(data, creationTime, expireTime)
class WtSessionTicket(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime) internal class WtSessionTicket(data: ByteArray, creationTime: Long) : KeyWithCreationTime(data, creationTime)
open class KeyWithExpiry( internal open class KeyWithExpiry(
data: ByteArray, data: ByteArray,
creationTime: Long, creationTime: Long,
val expireTime: Long val expireTime: Long
) : KeyWithCreationTime(data, creationTime) ) : KeyWithCreationTime(data, creationTime)
open class KeyWithCreationTime( internal open class KeyWithCreationTime(
val data: ByteArray, val data: ByteArray,
val creationTime: Long val creationTime: Long
) )
\ No newline at end of file
...@@ -5,6 +5,7 @@ import kotlinx.serialization.Serializable ...@@ -5,6 +5,7 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumberType import kotlinx.serialization.protobuf.ProtoNumberType
import kotlinx.serialization.protobuf.ProtoType import kotlinx.serialization.protobuf.ProtoType
import net.mamoe.mirai.qqandroid.io.ProtoBuf import net.mamoe.mirai.qqandroid.io.ProtoBuf
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
@Serializable @Serializable
...@@ -624,7 +625,7 @@ internal class ImMsgBody : ProtoBuf { ...@@ -624,7 +625,7 @@ internal class ImMsgBody : ProtoBuf {
internal class NotOnlineImage( internal class NotOnlineImage(
@SerialId(1) val filePath: String = "", @SerialId(1) val filePath: String = "",
@SerialId(2) val fileLen: Int = 0, @SerialId(2) val fileLen: Int = 0,
@SerialId(3) val downloadPath: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(3) val downloadPath: String = "",
@SerialId(4) val oldVerSendFile: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(4) val oldVerSendFile: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val imgType: Int = 0, @SerialId(5) val imgType: Int = 0,
@SerialId(6) val previewsImage: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(6) val previewsImage: ByteArray = EMPTY_BYTE_ARRAY,
...@@ -653,6 +654,20 @@ internal class ImMsgBody : ProtoBuf { ...@@ -653,6 +654,20 @@ internal class ImMsgBody : ProtoBuf {
@SerialId(29) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY @SerialId(29) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable // 非官方.
internal data class PbReserve(
@SerialId(1) val unknown1: Int = 1,
@SerialId(2) val unknown2: Int = 0,
@SerialId(6) val unknown3: Int = 0,
@SerialId(8) val hint: String = "[动画表情]",
@SerialId(10) val unknown5: Int = 0,
@SerialId(15) val unknwon6: Int = 5
) : ProtoBuf {
companion object {
val DEFAULT: ByteArray = PbReserve().toByteArray(serializer())
}
}
@Serializable @Serializable
internal class OnlineImage( internal class OnlineImage(
@SerialId(1) val guid: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(1) val guid: ByteArray = EMPTY_BYTE_ARRAY,
......
package net.mamoe.mirai.qqandroid.network.protocol.data.proto
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
import net.mamoe.mirai.qqandroid.io.ProtoBuf
@Serializable
class SyncCookie(
@SerialId(2) val time: Long,
@SerialId(3) val unknown1: Long = 2994099792,
@SerialId(4) val unknown2: Long = 3497826378,
@SerialId(5) val const1: Long = 1680172298,
@SerialId(6) val const2: Long = 2424173273,
@SerialId(7) val unknown3: Long = 83,
@SerialId(8) val unknown4: Long = 0
) : ProtoBuf
\ No newline at end of file
...@@ -192,7 +192,7 @@ internal inline fun PacketFactory<*>.buildLoginOutgoingPacket( ...@@ -192,7 +192,7 @@ internal inline fun PacketFactory<*>.buildLoginOutgoingPacket(
}) })
} }
private val BRP_STUB = ByteReadPacket(EMPTY_BYTE_ARRAY) private inline val BRP_STUB get() = ByteReadPacket.Empty
/** /**
* The second outermost packet for login * The second outermost packet for login
...@@ -233,7 +233,8 @@ internal inline fun BytePacketBuilder.writeSsoPacket( ...@@ -233,7 +233,8 @@ internal inline fun BytePacketBuilder.writeSsoPacket(
writeInt(subAppId.toInt()) writeInt(subAppId.toInt())
writeInt(subAppId.toInt()) writeInt(subAppId.toInt())
writeHex(unknownHex) writeHex(unknownHex)
if (extraData === BRP_STUB) { if (extraData === BRP_STUB || extraData.remaining == 0L) {
// fast-path
writeInt(0x04) writeInt(0x04)
} else { } else {
writeInt((extraData.remaining + 4).toInt()) writeInt((extraData.remaining + 4).toInt())
......
...@@ -9,7 +9,8 @@ import net.mamoe.mirai.message.data.MessageChain ...@@ -9,7 +9,8 @@ import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.event.ForceOfflineEvent import net.mamoe.mirai.qqandroid.event.ForceOfflineEvent
import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket
import net.mamoe.mirai.qqandroid.io.serialization.readRemainingAsProtoBuf import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.io.serialization.writeProtoBuf import net.mamoe.mirai.qqandroid.io.serialization.writeProtoBuf
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushForceOffline import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushForceOffline
...@@ -17,6 +18,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPushNotify ...@@ -17,6 +18,7 @@ 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.ImMsgBody
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SyncCookie
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
...@@ -41,7 +43,7 @@ internal class MessageSvc { ...@@ -41,7 +43,7 @@ internal class MessageSvc {
override suspend fun QQAndroidBot.handle(packet: RequestPushNotify) { override suspend fun QQAndroidBot.handle(packet: RequestPushNotify) {
network.run { network.run {
PbGetMsg(client, packet).sendAndExpect<MultiPacket<FriendMessage>>() PbGetMsg(client, packet.stMsgInfo?.uMsgTime ?: 0).sendAndExpect<MultiPacket<FriendMessage>>()
} }
} }
} }
...@@ -56,7 +58,7 @@ internal class MessageSvc { ...@@ -56,7 +58,7 @@ internal class MessageSvc {
operator fun invoke( operator fun invoke(
client: QQAndroidClient, client: QQAndroidClient,
from: RequestPushNotify msgTime: Long //PbPushMsg.msg.msgHead.msgTime
): OutgoingPacket = buildOutgoingUniPacket( ): OutgoingPacket = buildOutgoingUniPacket(
client, client,
extraData = EXTRA_DATA.toReadPacket() extraData = EXTRA_DATA.toReadPacket()
...@@ -64,14 +66,16 @@ internal class MessageSvc { ...@@ -64,14 +66,16 @@ internal class MessageSvc {
writeProtoBuf( writeProtoBuf(
MsgSvc.PbGetMsgReq.serializer(), MsgSvc.PbGetMsgReq.serializer(),
MsgSvc.PbGetMsgReq( MsgSvc.PbGetMsgReq(
msgReqType = from.ctype.toInt(), msgReqType = 1, // from.ctype.toInt()
contextFlag = 1, contextFlag = 1,
rambleFlag = 0, rambleFlag = 0,
latestRambleNumber = 20, latestRambleNumber = 20,
otherRambleNumber = 3, otherRambleNumber = 3,
onlineSyncFlag = 1, onlineSyncFlag = 1,
whisperSessionId = 0,
// serverBuf = from.serverBuf ?: EMPTY_BYTE_ARRAY, // serverBuf = from.serverBuf ?: EMPTY_BYTE_ARRAY,
syncCookie = client.c2cMessageSync.syncCookie, syncCookie = client.c2cMessageSync.syncCookie
?: SyncCookie(msgTime).toByteArray(SyncCookie.serializer()).also { client.c2cMessageSync.syncCookie = it },
syncFlag = 1 syncFlag = 1
// syncFlag = client.c2cMessageSync.syncFlag, // syncFlag = client.c2cMessageSync.syncFlag,
//msgCtrlBuf = client.c2cMessageSync.msgCtrlBuf, //msgCtrlBuf = client.c2cMessageSync.msgCtrlBuf,
...@@ -83,7 +87,11 @@ internal class MessageSvc { ...@@ -83,7 +87,11 @@ internal class MessageSvc {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): MultiPacket<FriendMessage> { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): MultiPacket<FriendMessage> {
// 00 00 01 0F 08 00 12 00 1A 34 08 FF C1 C4 F1 05 10 FF C1 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 8A CA 91 D1 0C 48 9B A5 BD 9B 0A 58 DE 9D 99 F8 08 60 1D 68 FF C1 C4 F1 05 70 00 20 02 2A 9D 01 08 F3 C1 C4 F1 05 10 A2 FF 8C F0 03 18 01 22 8A 01 0A 2A 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 A6 01 20 0B 28 AE F9 01 30 F4 C1 C4 F1 05 38 A7 E3 D8 D4 84 80 80 80 01 B8 01 CD B5 01 12 08 08 01 10 00 18 00 20 00 1A 52 0A 50 0A 27 08 00 10 F4 C1 C4 F1 05 18 A7 E3 D8 D4 04 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 08 0A 06 0A 04 4E 4D 53 4C 12 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 12 04 4A 02 08 00 30 01 2A 15 08 97 A2 C1 F1 05 10 95 A6 F5 E5 0C 18 01 30 01 40 01 48 81 01 2A 10 08 D3 F7 B5 F1 05 10 DD F1 92 B7 07 18 01 30 01 38 00 42 00 48 00 // 00 00 01 0F 08 00 12 00 1A 34 08 FF C1 C4 F1 05 10 FF C1 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 8A CA 91 D1 0C 48 9B A5 BD 9B 0A 58 DE 9D 99 F8 08 60 1D 68 FF C1 C4 F1 05 70 00 20 02 2A 9D 01 08 F3 C1 C4 F1 05 10 A2 FF 8C F0 03 18 01 22 8A 01 0A 2A 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 A6 01 20 0B 28 AE F9 01 30 F4 C1 C4 F1 05 38 A7 E3 D8 D4 84 80 80 80 01 B8 01 CD B5 01 12 08 08 01 10 00 18 00 20 00 1A 52 0A 50 0A 27 08 00 10 F4 C1 C4 F1 05 18 A7 E3 D8 D4 04 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 08 0A 06 0A 04 4E 4D 53 4C 12 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 12 04 4A 02 08 00 30 01 2A 15 08 97 A2 C1 F1 05 10 95 A6 F5 E5 0C 18 01 30 01 40 01 48 81 01 2A 10 08 D3 F7 B5 F1 05 10 DD F1 92 B7 07 18 01 30 01 38 00 42 00 48 00
discardExact(4) discardExact(4)
val resp = readRemainingAsProtoBuf(MsgSvc.PbGetMsgResp.serializer()) val resp = readProtoBuf(MsgSvc.PbGetMsgResp.serializer())
if (resp.result != 0) {
return MultiPacket(emptyList())
}
bot.client.c2cMessageSync.syncCookie = resp.syncCookie bot.client.c2cMessageSync.syncCookie = resp.syncCookie
bot.client.c2cMessageSync.pubAccountCookie = resp.pubAccountCookie bot.client.c2cMessageSync.pubAccountCookie = resp.pubAccountCookie
...@@ -193,7 +201,7 @@ internal class MessageSvc { ...@@ -193,7 +201,7 @@ internal class MessageSvc {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
discardExact(4) discardExact(4)
val response = readRemainingAsProtoBuf(MsgSvc.PbSendMsgResp.serializer()) val response = readProtoBuf(MsgSvc.PbSendMsgResp.serializer())
return if (response.result == 0) { return if (response.result == 0) {
Response.SUCCESS Response.SUCCESS
} else { } else {
......
...@@ -3,7 +3,10 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.list ...@@ -3,7 +3,10 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.list
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.data.Packet import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.* import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket
import net.mamoe.mirai.qqandroid.io.serialization.jceRequestSBuffer
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.io.serialization.writeJceStruct
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.GetFriendListReq import net.mamoe.mirai.qqandroid.network.protocol.data.jce.GetFriendListReq
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.GetFriendListResp import net.mamoe.mirai.qqandroid.network.protocol.data.jce.GetFriendListResp
......
...@@ -20,7 +20,7 @@ import net.mamoe.mirai.utils.io.toReadPacket ...@@ -20,7 +20,7 @@ import net.mamoe.mirai.utils.io.toReadPacket
import net.mamoe.mirai.utils.localIpAddress import net.mamoe.mirai.utils.localIpAddress
@Suppress("EnumEntryName") @Suppress("EnumEntryName")
enum class RegPushReason { internal enum class RegPushReason {
appRegister, appRegister,
createDefaultRegInfo, createDefaultRegInfo,
fillRegProxy, fillRegProxy,
...@@ -32,7 +32,7 @@ enum class RegPushReason { ...@@ -32,7 +32,7 @@ enum class RegPushReason {
unknown unknown
} }
class StatSvc { internal class StatSvc {
internal object Register : PacketFactory<Register.Response>("StatSvc.register") { internal object Register : PacketFactory<Register.Response>("StatSvc.register") {
internal object Response : Packet { internal object Response : Packet {
......
...@@ -3,6 +3,7 @@ package net.mamoe.mirai.qqandroid.utils ...@@ -3,6 +3,7 @@ package net.mamoe.mirai.qqandroid.utils
import net.mamoe.mirai.data.ImageLink import net.mamoe.mirai.data.ImageLink
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.utils.io.hexToBytes
internal fun MessageChain.toRichTextElems(): MutableList<ImMsgBody.Elem> { internal fun MessageChain.toRichTextElems(): MutableList<ImMsgBody.Elem> {
...@@ -20,7 +21,16 @@ internal fun MessageChain.toRichTextElems(): MutableList<ImMsgBody.Elem> { ...@@ -20,7 +21,16 @@ internal fun MessageChain.toRichTextElems(): MutableList<ImMsgBody.Elem> {
elems.add( elems.add(
ImMsgBody.Elem( ImMsgBody.Elem(
notOnlineImage = ImMsgBody.NotOnlineImage( notOnlineImage = ImMsgBody.NotOnlineImage(
filePath = it.id.value filePath = it.id.value, // 错了, 应该是 2B23D705CAD1F2CF3710FE582692FCC4.jpg
fileLen = 1149, // 假的
downloadPath = it.id.value,
imgType = 1000, // 不确定
picMd5 = "2B 23 D7 05 CA D1 F2 CF 37 10 FE 58 26 92 FC C4".hexToBytes(),
picHeight = 66,
picWidth = 66,
resId = it.id.value,
bizType = 5,
pbReserve = ImMsgBody.PbReserve.DEFAULT // 可能还可以改变 `[动画表情]`
) )
) )
) )
......
package net.mamoe.mirai.qqandroid.utils
import net.mamoe.mirai.utils.io.getRandomByteArray
import net.mamoe.mirai.utils.md5
fun generateTgtgtKey(guid: ByteArray): ByteArray =
md5(getRandomByteArray(16) + guid)
...@@ -20,7 +20,9 @@ class JceDecoderTest { ...@@ -20,7 +20,9 @@ class JceDecoderTest {
@SerialId(3) val int: Int = 123, @SerialId(3) val int: Int = 123,
@SerialId(4) val long: Long = 123, @SerialId(4) val long: Long = 123,
@SerialId(5) val float: Float = 123f, @SerialId(5) val float: Float = 123f,
@SerialId(6) val double: Double = 123.0 @SerialId(6) val double: Double = 123.0,
@SerialId(7) val byteArray: ByteArray = byteArrayOf(1, 2, 3),
@SerialId(8) val byteArray2: ByteArray = byteArrayOf(1, 2, 3)
) : JceStruct { ) : JceStruct {
override fun writeTo(output: JceOutput) = output.run { override fun writeTo(output: JceOutput) = output.run {
writeString(string, 0) writeString(string, 0)
...@@ -30,10 +32,81 @@ class JceDecoderTest { ...@@ -30,10 +32,81 @@ class JceDecoderTest {
writeLong(long, 4) writeLong(long, 4)
writeFloat(float, 5) writeFloat(float, 5)
writeDouble(double, 6) writeDouble(double, 6)
writeFully(byteArray, 7)
writeFully(byteArray2, 8)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as TestSimpleJceStruct
if (string != other.string) return false
if (byte != other.byte) return false
if (short != other.short) return false
if (int != other.int) return false
if (long != other.long) return false
if (float != other.float) return false
if (double != other.double) return false
if (!byteArray.contentEquals(other.byteArray)) return false
if (!byteArray2.contentEquals(other.byteArray2)) return false
return true
}
override fun hashCode(): Int {
var result = string.hashCode()
result = 31 * result + byte
result = 31 * result + short
result = 31 * result + int
result = 31 * result + long.hashCode()
result = 31 * result + float.hashCode()
result = 31 * result + double.hashCode()
result = 31 * result + byteArray.contentHashCode()
result = 31 * result + byteArray2.contentHashCode()
return result
} }
} }
@Test
fun testByteArray() {
@Serializable
data class TestByteArray(
@SerialId(0) val byteArray: ByteArray = byteArrayOf(1, 2, 3)
) : JceStruct {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as TestByteArray
if (!byteArray.contentEquals(other.byteArray)) return false
return true
}
override fun hashCode(): Int {
return byteArray.contentHashCode()
}
}
assertEquals(
TestByteArray(),
TestByteArray().toByteArray(TestByteArray.serializer()).loadAs(TestByteArray.serializer())
)
}
@Test
fun testSimpleStruct() {
assertEquals(
TestSimpleJceStruct(),
TestSimpleJceStruct().toByteArray(TestSimpleJceStruct.serializer()).loadAs(TestSimpleJceStruct.serializer())
)
}
@Serializable @Serializable
class TestComplexJceStruct( class TestComplexJceStruct(
@SerialId(6) val string: String = "haha", @SerialId(6) val string: String = "haha",
...@@ -77,7 +150,7 @@ class JceDecoderTest { ...@@ -77,7 +150,7 @@ class JceDecoderTest {
@Test @Test
fun testNestedList() { fun testNestedList() {
@Serializable @Serializable
class TestNestedList( data class TestNestedList(
@SerialId(7) val array: List<List<Int>> = listOf(listOf(1, 2, 3), listOf(1, 2, 3), listOf(1, 2, 3)) @SerialId(7) val array: List<List<Int>> = listOf(listOf(1, 2, 3), listOf(1, 2, 3), listOf(1, 2, 3))
) : JceStruct ) : JceStruct
...@@ -133,6 +206,28 @@ class JceDecoderTest { ...@@ -133,6 +206,28 @@ class JceDecoderTest {
}.readBytes().loadAs(TestNestedMap.serializer()).map.entries.first().value.contentToString(), "{01=[0x0002(2)]}") }.readBytes().loadAs(TestNestedMap.serializer()).map.entries.first().value.contentToString(), "{01=[0x0002(2)]}")
} }
@Test
fun testMap3() {
@Serializable
class TestNestedMap(
@SerialId(7) val map: Map<Byte, ShortArray> = mapOf(1.toByte() to shortArrayOf(2))
) : JceStruct
assertEquals("{0x01(1)=[0x0002(2)]}", buildJcePacket {
writeMap(mapOf(1.toByte() to shortArrayOf(2)), 7)
}.readBytes().loadAs(TestNestedMap.serializer()).map.contentToString())
}
@Test
fun testNestedMap2() {
@Serializable
class TestNestedMap(
@SerialId(7) val map: Map<Int, Map<Byte, ShortArray>> = mapOf(1 to mapOf(1.toByte() to shortArrayOf(2)))
) : JceStruct
assertEquals(buildJcePacket {
writeMap(mapOf(1 to mapOf(1.toByte() to shortArrayOf(2))), 7)
}.readBytes().loadAs(TestNestedMap.serializer()).map.entries.first().value.contentToString(), "{0x01(1)=[0x0002(2)]}")
}
@Test @Test
fun testNullableEncode() { fun testNullableEncode() {
...@@ -186,6 +281,9 @@ class JceDecoderTest { ...@@ -186,6 +281,9 @@ class JceDecoderTest {
@SerialId(0) val innerStructList: List<TestSimpleJceStruct> @SerialId(0) val innerStructList: List<TestSimpleJceStruct>
) : JceStruct ) : JceStruct
println(buildJcePacket {
writeCollection(listOf(TestSimpleJceStruct(), TestSimpleJceStruct()), 0)
}.readBytes().loadAs(OuterStruct.serializer()).innerStructList.toString())
assertEquals( assertEquals(
buildJcePacket { buildJcePacket {
writeCollection(listOf(TestSimpleJceStruct(), TestSimpleJceStruct()), 0) writeCollection(listOf(TestSimpleJceStruct(), TestSimpleJceStruct()), 0)
......
...@@ -55,6 +55,7 @@ internal class TIMPCBotNetworkHandler internal constructor(coroutineContext: Cor ...@@ -55,6 +55,7 @@ internal class TIMPCBotNetworkHandler internal constructor(coroutineContext: Cor
private var heartbeatJob: Job? = null private var heartbeatJob: Job? = null
@MiraiInternalAPI
override suspend fun login() { override suspend fun login() {
TIMProtocol.SERVER_IP.shuffled().forEach { ip -> TIMProtocol.SERVER_IP.shuffled().forEach { ip ->
......
...@@ -27,7 +27,8 @@ abstract class BotImpl<N : BotNetworkHandler> constructor( ...@@ -27,7 +27,8 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
@Suppress("CanBePrimaryConstructorProperty") // for logger @Suppress("CanBePrimaryConstructorProperty") // for logger
final override val account: BotAccount = account final override val account: BotAccount = account
@UseExperimental(RawAccountIdUse::class) @UseExperimental(RawAccountIdUse::class)
override val uin: Long get() = account.id override val uin: Long
get() = account.id
final override val logger: MiraiLogger by lazy { configuration.logger ?: DefaultLogger("Bot($uin)").also { configuration.logger = it } } final override val logger: MiraiLogger by lazy { configuration.logger ?: DefaultLogger("Bot($uin)").also { configuration.logger = it } }
init { init {
...@@ -98,16 +99,28 @@ abstract class BotImpl<N : BotNetworkHandler> constructor( ...@@ -98,16 +99,28 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
} }
_network = createNetworkHandler(this.coroutineContext) _network = createNetworkHandler(this.coroutineContext)
while (true){ loginLoop@ while (true) {
try { try {
return _network.login() _network.login()
} catch (e: Exception){ break@loginLoop
} catch (e: Exception) {
e.logStacktrace() e.logStacktrace()
_network.dispose(e) _network.dispose(e)
} }
logger.warning("Login failed. Retrying in 3s...") logger.warning("Login failed. Retrying in 3s...")
delay(3000) delay(3000)
} }
while (true) {
try {
return _network.init()
} catch (e: Exception) {
e.logStacktrace()
_network.dispose(e)
}
logger.warning("Init failed. Retrying in 3s...")
delay(3000)
}
} }
protected abstract fun createNetworkHandler(coroutineContext: CoroutineContext): N protected abstract fun createNetworkHandler(coroutineContext: CoroutineContext): N
......
...@@ -157,7 +157,7 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>( ...@@ -157,7 +157,7 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>(
ListeningFilter { !filter.invoke(this, it) || !another.filter.invoke(this, it) } ListeningFilter { !filter.invoke(this, it) || !another.filter.invoke(this, it) }
/** /**
* 启动时间监听. * 启动事件监听.
*/ */
// do not inline due to kotlin (1.3.61) bug: java.lang.IllegalAccessError // do not inline due to kotlin (1.3.61) bug: java.lang.IllegalAccessError
operator fun invoke(onEvent: MessageListener<T>): Listener<T> { operator fun invoke(onEvent: MessageListener<T>): Listener<T> {
......
...@@ -14,7 +14,7 @@ fun Image(id: String) = Image(ImageId(id)) ...@@ -14,7 +14,7 @@ fun Image(id: String) = Image(ImageId(id))
* 由接收消息时构建, 可直接发送 * 由接收消息时构建, 可直接发送
* *
* @param id 这个图片的 [ImageId] * @param id 这个图片的 [ImageId]
*/ */ // TODO: 2020/1/31 去掉 Image. 将 Image 改为 interface/class
inline class Image(inline val id: ImageId) : Message { inline class Image(inline val id: ImageId) : Message {
override fun toString(): String = "[${id.value}]" override fun toString(): String = "[${id.value}]"
......
...@@ -47,6 +47,13 @@ abstract class BotNetworkHandler : CoroutineScope { ...@@ -47,6 +47,13 @@ abstract class BotNetworkHandler : CoroutineScope {
@MiraiInternalAPI @MiraiInternalAPI
abstract suspend fun login() abstract suspend fun login()
/**
* 初始化获取好友列表等值.
*/
@MiraiInternalAPI
open suspend fun init() {
}
/** /**
* 等待直到与服务器断开连接. 若未连接则立即返回 * 等待直到与服务器断开连接. 若未连接则立即返回
*/ */
......
...@@ -54,6 +54,10 @@ fun ByteReadPacket.readIoBuffer( ...@@ -54,6 +54,10 @@ fun ByteReadPacket.readIoBuffer(
n: Int = remaining.toInt()//not that safe but adequate n: Int = remaining.toInt()//not that safe but adequate
): IoBuffer = IoBuffer.Pool.borrow().also { this.readFully(it, n) } ): IoBuffer = IoBuffer.Pool.borrow().also { this.readFully(it, n) }
fun ByteReadPacket.readPacket(
n: Int = remaining.toInt()//not that safe but adequate
): ByteReadPacket = this.readBytes(n).toReadPacket()
fun ByteReadPacket.readIoBuffer(n: Short) = this.readIoBuffer(n.toInt()) fun ByteReadPacket.readIoBuffer(n: Short) = this.readIoBuffer(n.toInt())
fun Input.readIP(): String = buildString(4 + 3) { fun Input.readIP(): String = buildString(4 + 3) {
......
...@@ -130,7 +130,7 @@ fun Bot.messageDSL() { ...@@ -130,7 +130,7 @@ fun Bot.messageDSL() {
// 当消息中包含 "复读" 时 // 当消息中包含 "复读" 时
contains("复读") { val listener = (contains("复读1") or contains("复读2")) {
reply(message) reply(message)
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment