New PacketFactory structure

This commit is contained in:
Him188 2020-02-02 16:02:17 +08:00
parent 26c86129c6
commit 63a4d5f3b9
16 changed files with 290 additions and 418 deletions

View File

@ -104,7 +104,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
}
override suspend fun init() {
delay(5000)
// delay(5000)
this@QQAndroidBotNetworkHandler.subscribeAlways<ForceOfflineEvent> {
if (this@QQAndroidBotNetworkHandler.bot == this.bot) {
@ -126,7 +126,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
20,
0,
0
).sendAndExpect<FriendList.GetFriendGroupList.Response>()
).sendAndExpect<FriendList.GetFriendGroupList.Response>(timeoutMillis = 1000)
totalFriendCount = data.totalFriendCount
data.friendList.forEach {
@ -150,7 +150,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
bot.logger.info("开始加载组信息")
val troopData = FriendList.GetTroopListSimplify(
bot.client
).sendAndExpect<FriendList.GetTroopListSimplify.Response>()
).sendAndExpect<FriendList.GetTroopListSimplify.Response>(timeoutMillis = 1000)
println(troopData.contentToString())
} catch (e: Exception) {
bot.logger.info("加载组信息失败|一般这是由于加载过于频繁导致/将以热加载方式加载群列表")
@ -235,7 +235,10 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
bot.logger.info("Received packet: $packet")
packetFactory?.run {
bot.handle(packet)
when (this) {
is OutgoingPacketFactory<P> -> bot.handle(packet)
is IncomingPacketFactory<P> -> bot.handle(packet, sequenceId)?.sendWithoutExpect()
}
}
}

View File

@ -1,116 +0,0 @@
# QQAndroid Protocol
## Overview
Note: `head` and `body` functions do nothing. They just work as
notations
PseudoCode:
```
OutgoingPacket {
int head.size + body.size + 4
head {
int 0x0A
byte 0x02
int extra data size + 4
byte[] extra data // initially={}
byte 0
int uinAccount.length + 4
byte[] uinAccount // =qqNumber.toString()
}
body { // encrypted by `ByteArray(16)` when login, after which by sessionKey
SSOPacket {
int head.size + 4
head {
int sequenceId
int subAppId
int subAppId
hex "01 00 00 00 00 00 00 00 00 00 01 00" // unknown values
int extraData.size + 4
byte[] extraData // empty when login
int commandName.length + 4
byte[] commandName // e.g. wtlogin.login
int 4 + 4
int 0x02B05B8B
int imei.length + 4
byte[] imei
int 0 + 4
short ksid.length + 2
byte[] ksid
int 0 + 4
}
int body.size + 4
body {
OicqRequestPacket {
head {
byte 2 // head flag
short 27 + 2 + remaining.length
ushort client.protocolVersion // const 8001
ushort commandId // e.g. 0x0810
ushort 0x0001
uint client.uin
byte 3 // const
ubyte encryptMethod.value // [EncryptMethod]
byte 0 // const
int 2 // const
int client.appClientVersion
int 0 // const
}
body {
// only write one of the following two structures!!
// if encryption method is ECDH
EncryptionMethodECDH {
head {
byte 1
byte 1
byte[] privateKey // random key
short 258
short [ECDH.publicKey].size // always 49
byte[] [ECDH.publicKey]
}
body {
// real body
}
}
// if encryption method is SessionKey
EncryptionMethodSessionKey {
head {
byte 1
byte if (currentLoginState == 2) 3 else 2
fully key
short 258 // const
short 0
}
body {
// real body
}
}
}
tail {
byte 3 // tail flag
}
}
}
}
}
}
```
## Packet bodies
### LoginPacket - SubCommand 9
**TO BE UPDATED**
PseudoCode:
```
short 9 // subCommand
tlvList {
}
```

View File

@ -0,0 +1,2 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet

View File

@ -12,10 +12,6 @@ import net.mamoe.mirai.utils.io.writeHex
import net.mamoe.mirai.utils.io.writeIntLVPacket
import net.mamoe.mirai.utils.io.writeQQ
/**
* 待发送给服务器的数据包. 它代表着一个 [ByteReadPacket].
* 只有最终的包才会被包装为 [OutgoingPacket].
*/
internal class OutgoingPacket constructor(
name: String?,
val commandName: String,
@ -31,25 +27,11 @@ internal val KEY_16_ZEROS = ByteArray(16)
internal val EMPTY_BYTE_ARRAY = ByteArray(0)
/**
* 最外层的包. 结构适用于登录之后的过程.
*
* QQ 中这个被以 JNI 实现:
* com.tencent.qphone.base.util.CodecWarpper#encodeRequest(int, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, byte[], int, int, java.lang.String, byte, byte, byte, byte[], byte[], boolean)
*
* **Packet structure**
* int remaining.length + 4
* int 0x0B
* byte 0x01
* int sequenceId
* byte 0
* int uinAccount.length + 4
* byte[] uinAccount
*
* byte[] body encrypted by 16 zero
*/
@Deprecated("危险", level = DeprecationLevel.ERROR)
@UseExperimental(MiraiInternalAPI::class)
internal inline fun PacketFactory<*>.buildOutgoingPacket(
internal inline fun OutgoingPacketFactory<*>.buildOutgoingPacket(
client: QQAndroidClient,
bodyType: Byte = 1, // 1: PB?
name: String? = this.commandName,
@ -76,11 +58,8 @@ internal inline fun PacketFactory<*>.buildOutgoingPacket(
})
}
/**
* buildOutgoingPacket writeUniPacket fast-path
*/
@UseExperimental(MiraiInternalAPI::class)
internal inline fun PacketFactory<*>.buildOutgoingUniPacket(
internal inline fun OutgoingPacketFactory<*>.buildOutgoingUniPacket(
client: QQAndroidClient,
bodyType: Byte = 1, // 1: PB?
name: String? = this.commandName,
@ -110,6 +89,37 @@ internal inline fun PacketFactory<*>.buildOutgoingUniPacket(
})
}
@UseExperimental(MiraiInternalAPI::class)
internal inline fun IncomingPacketFactory<*>.buildResponseUniPacket(
client: QQAndroidClient,
bodyType: Byte = 1, // 1: PB?
name: String? = this.responseCommandName,
commandName: String = this.responseCommandName,
key: ByteArray = client.wLoginSigInfo.d2Key,
extraData: ByteReadPacket = BRP_STUB,
sequenceId: Int = client.nextSsoSequenceId(),
body: BytePacketBuilder.(sequenceId: Int) -> Unit
): OutgoingPacket {
return OutgoingPacket(name, commandName, sequenceId, buildPacket {
writeIntLVPacket(lengthOffset = { it + 4 }) {
writeInt(0x0B)
writeByte(bodyType)
writeInt(sequenceId)
writeByte(0)
client.uin.toString().let {
writeInt(it.length + 4)
writeStringUtf8(it)
}
encryptAndWrite(key) {
writeUniPacket(commandName, client.outgoingPacketUnknownValue, extraData) {
body(sequenceId)
}
}
}
})
}
@UseExperimental(MiraiInternalAPI::class)
internal inline fun BytePacketBuilder.writeUniPacket(
commandName: String,
@ -140,25 +150,10 @@ internal inline fun BytePacketBuilder.writeUniPacket(
/**
* 最外层的包. 结构适用于登录.
*
* QQ 中这个被以 JNI 实现:
* com.tencent.qphone.base.util.CodecWarpper#encodeRequest(int, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, byte[], int, int, java.lang.String, byte, byte, byte, byte[], byte[], boolean)
*
* **Packet structure**
* int remaining.length + 4
* int 0x0A
* byte 0x02
* int extra data size + 4
* byte[] extra data
* byte 0
* int uinAccount.length + 4
* byte[] uinAccount
*
* byte[] body encrypted by 16 zero
*/
@UseExperimental(MiraiInternalAPI::class)
internal inline fun PacketFactory<*>.buildLoginOutgoingPacket(
internal inline fun OutgoingPacketFactory<*>.buildLoginOutgoingPacket(
client: QQAndroidClient,
bodyType: Byte,
extraData: ByteArray = EMPTY_BYTE_ARRAY,
@ -193,30 +188,6 @@ internal inline fun PacketFactory<*>.buildLoginOutgoingPacket(
private inline val BRP_STUB get() = ByteReadPacket.Empty
/**
* The second outermost packet for login
*
* int headRemaining.size+4
* int sequenceId
* int subAppId
* int subAppId
* hex "01 00 00 00 00 00 00 00 00 00 01 00" // unknown values
* int extraData.size+4
* byte[] extraData
* int commandName.length+4
* byte[] commandName
* int 4+4
* int 0x02B05B8B
* int imei.length+4
* byte[] imei
* int 0+4
* int ksid.length+4
* byte[] ksid
* int 0+4
*
* int bodyRemaining.size+4
* byte[] body()
*/
@UseExperimental(MiraiInternalAPI::class)
internal inline fun BytePacketBuilder.writeSsoPacket(
client: QQAndroidClient,
@ -266,26 +237,6 @@ internal inline fun BytePacketBuilder.writeSsoPacket(
writeIntLVPacket(lengthOffset = { it + 4 }, builder = body)
}
/**
* Writes a request packet
* This is the innermost packet structure
*
* **Packet Structure**
* byte 2 // head flag
* short 27 + 2 + remaining.length
* ushort client.protocolVersion // const 8001
* ushort 0x0001
* uint client.uin
* byte 3 // const
* ubyte encryptMethod.value // [EncryptMethod]
* byte 0 // const
* int 2 // const
* int client.appClientVersion
* int 0 // const
* bodyBlock()
* byte 3 // tail
*/
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
internal fun BytePacketBuilder.writeOicqRequestPacket(
client: QQAndroidClient,
@ -294,8 +245,6 @@ internal fun BytePacketBuilder.writeOicqRequestPacket(
bodyBlock: BytePacketBuilder.() -> Unit
) {
val body = encryptMethod.makeBody(client, bodyBlock)
// writeIntLVPacket(lengthOffset = { it + 4 }) {
// Head
writeByte(0x02) // head
writeShort((27 + 2 + body.remaining).toShort()) // orthodox algorithm
writeShort(client.protocolVersion)
@ -309,72 +258,7 @@ internal fun BytePacketBuilder.writeOicqRequestPacket(
writeInt(client.appClientVersion)
writeInt(0) // constp_always_0
// Body
writePacket(body)
// Tail
writeByte(0x03) // tail
// }
}
/*
00 00 01 64
00 00 00 0A
02
00 00 00 04
00
00 00 00 0E
31 39 39 34 37 30 31 30 32 31
// encrypted with 16zero
F8 22 FC 39 2E 93 D7 73 A9 75 A2 D4 67 D2 C4 0D F1 02 1F A5 74 8F D8 0E 8E 86 AF 4F 4A A9 C7 74 56 71 B9 03 FC B3 DE A0 F3 14 B7 E9 54 3B 22 F0 24 10 BD 52 88 FC F3 58 66 6C B9 DB 4D 45 2C EF DE 2C C9 E1 1B 27 C7 E2 EF 38 6A 7E 8B 52 3A F4 93 40 E1 A9 ED 10 C3 A3 7E 64 17 02 8F 5C 01 92 72 C7 B8 E0 E1 A5 AF 0B 27 D0 05 C1 33 37 77 37 6D 96 0B B4 1F 41 98 42 35 2C 2A 00 E4 ED E8 C6 42 C4 F4 FD 13 39 D8 E8 19 50 E9 49 06 37 CA CF 42 C3 DD B5 DC B0 E9 87 83 6E 77 AE B6 5C F5 0D 6A 08 67 D0 61 B0 86 39 F7 2E AF E7 B7 C5 F4 42 40 A1 E1 A9 90 55 26 BD C6 03 73 73 BF A2 0A 3F E6 D3 8D B3 69 63 81 83 1E F1 72 5D FA FC 5E 65 B9 C1 FE 77 A8 50 80 F1 A5 DF E0 C4 96 1D 21 CD 5B 70 62 35 51 B5 37 1F 0B 4A 6D 97 92 D0 33 2B 56 11 CB 54 E5 6A A4 B9 97 04 B3 4B 27 A6 61 B7 77 5C C0 D1 6B 98 1C 7A 7B 57 28 3B 80 3B 81 88 69 D2 1C 91 B8 4A DE 0F FD A2 82 F8 3B F6 61 90 84 EF 4A 17 B6 30 1D 09 62 11 C7 BB 00 76 8E 0D 48 1B 11 F4 90 7A 13 0F 09 2B 4E 2F BE FD D9 57 07 18 29 4C 52 23 2E AE
//decrypted:
00 00 00 C1
00 01 4E 6A
20 02 ED BD
20 02 ED BD
01 00 00 00 00 00 00 00 00 00 01 00
00 00 00 4C
B8 12 0D E1 DA 19 AF D3 EB 36 76 BD 42 08 F6 DC A5 35 69 C0 8F F2 75 28 B4 CE 09 C9 B7 86 E3 5A 14 D1 0D CA 5D D4 CB 16 77 8B 32 8D 81 3B 3F D9 52 13 77 03 D3 F7 0E CD 7B 21 95 D2 59 CE 0C 31 D6 F1 38 2A FA 82 AD 60
00 00 00 14
47 72 61 79 55 69 6E 50 72 6F 2E 43 68 65 63 6B // serviceCommand
00 00 00 08
02 B0 5B 8B
00 00 00 13
38 35 38 34 31 34 33 36 39 32 31 31 39 39 33
00 00 00 04
00 22
7C 34 35 34 30 30 31 32 32 38 34 33 37 35 39 30 7C 41 38 2E 32 2E 30 2E 32 37 66 36 65 61 39 36 00 00 00 04
00 00 00 7A // UniPacket
10
03 2C
3C 42 00 01 4E 69 56 22 4B 51 51 2E 43 6F 6E 66 69 67 53 65 72 76 69 63 65 2E 43 6F 6E 66 69 67 53 65 72 76 61 6E 74 4F 62 6A 66 09 43 6C 69 65 6E 74 52 65 71 7D 00 00 35 08 00 01 06 03 72 65 71 1D 00 00 29 0A 12 20 02 ED BD 26 0A 31 39 39 34 37 30 31 30 32 31 36 00 46 12 31 30 31 31 30 33 30 38 33 38 34 36 30 36 32 30 34 32 0B 8C 98 0C A8 0C
*/
/*
00 00 00 FC
00 00 00 0B
01 // packet type?
00 01 50 DE
00
00 00 00 0E
31 39 39 34 37 30 31 30 32 31
4E 32 1B 0F 07 DC 39 FE 14 78 ED 32 60 C4 07 31 9D CD 1A E0 C4 F6 21 6B EA 52 A4 F4 C1 D2 AF FB 17 5A C4 15 BC 35 BC 45 58 B6 11 19 DA AF 12 91 B5 A0 5D E4 FD 5A 49 1A 55 71 45 89 6F 3A 09 E6 32 F4 96 4A BB B2 EE 35 B9 39 63 5B FF E3 F0 94 69 67 99 64 A2 03 23 D0 F7 74 81 D1 20 F8 20 E6 F3 5B E6 C2 A2 25 6F 90 C5 DA CB D2 08 9D 5D 83 47 F3 27 3F 41 19 E5 9A C0 F2 05 70 B2 C5 DC F9 F1 6D 2A E9 92 84 9C 8D 98 04 E8 A1 3B 40 F2 71 60 9F 2C D8 6A CD 6B F5 2B 12 68 C7 9C 6B 0E D2 F7 16 40 47 72 3D 6A AF 36 2E 43 0C 96 28 C7 A6 B1 04 3B 29 F6 8B A4 E0 47 1A 3D 51 32 C7 AF A5 7E FD F7 50 FC 81 3D 13 45 60 6B 8D F4 A6 9B E7 46 D4 1E 9B 2C 00 D0 24 2F 0E 44 29 43 A8 F6 25
*/
/*
00 00 01 04
00 00 00 0B
01
00 01 50 CE
00
00 00 00 0E 31 39 39 34 37 30 31 30 32 31 D2 D5 37 8A 3C 47 B1 84 E2 94 B2 AF BF 14 70 4D 73 17 BB 38 BE 82 73 DF A2 87 E0 0A 7A BA 8A 81 71 77 1D E1 71 7F B7 C1 66 1D 8C 3D 41 4F 51 09 6A B7 B7 7B 88 28 A6 5A AB 7E 40 25 9B C8 35 9C C6 E2 3A 5F 94 1D 70 0F D7 89 4D 41 6B 7A 29 A2 70 77 3D F8 1D 32 65 D7 D8 D1 6D 13 42 9C 0C 72 DB 48 95 4B 66 EF B9 E6 E4 C1 3B 2C 36 B0 D7 3F E2 85 C8 2A 8C 65 0F 0B 1C F1 A7 C7 E1 1F 0C 32 F5 08 14 AA 5A 43 CD 8E A8 82 14 24 97 63 F0 53 79 4E 33 8D 5F 1C F8 1C 89 3B 39 44 CC A7 63 5F FC BF 87 42 89 2D A5 F4 BC B2 69 49 54 DD AE E6 3F A2 A2 98 DC 3B D4 A2 27 10 F2 06 42 93 C5 30 4A D4 FA F5 BA A5 B2 4B 56 45 59 94 CA 4C 4B 17 55 C7 23 AF F0 8B E5 DC 3A 1B B6 A7 2E 10 BB 9A E7 70 54 BA F5 4B 70 91
*/
}

View File

@ -2,18 +2,18 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.writeFully
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.ProtoBufWithNullableSupport
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
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.buildLoginOutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x352Packet
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.GetImgUrlReq
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildLoginOutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.writeSsoPacket
internal object ImageDownPacket : PacketFactory<ImageDownPacket.ImageDownPacketResponse>("LongConn.OffPicDown") {
internal object ImageDownPacket : OutgoingPacketFactory<ImageDownPacket.ImageDownPacketResponse>("LongConn.OffPicDown") {
operator fun invoke(client: QQAndroidClient, req: GetImgUrlReq): OutgoingPacket {
// TODO: 2020/1/24 测试: bodyType, subAppId

View File

@ -2,18 +2,18 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.writeFully
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.ProtoBufWithNullableSupport
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.packet.*
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.buildLoginOutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x352Packet
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.UploadImgReq
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildLoginOutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.writeSsoPacket
internal object ImageUpPacket : PacketFactory<ImageUpPacket.ImageUpPacketResponse>("LongConn.OffPicUp") {
internal object ImageUpPacket : OutgoingPacketFactory<ImageUpPacket.ImageUpPacketResponse>("LongConn.OffPicUp") {
operator fun invoke(client: QQAndroidClient, req: UploadImgReq): OutgoingPacket {
// TODO: 2020/1/24 测试: bodyType, subAppId

View File

@ -19,9 +19,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SyncCookie
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.network.protocol.packet.*
import net.mamoe.mirai.qqandroid.utils.toMessageChain
import net.mamoe.mirai.qqandroid.utils.toRichTextElems
import net.mamoe.mirai.utils.MiraiInternalAPI
@ -36,19 +34,18 @@ internal class MessageSvc {
/**
* 告知要刷新好友消息
*/
internal object PushNotify : PacketFactory<RequestPushNotify>("MessageSvc.PushNotify") {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): RequestPushNotify {
internal object PushNotify : IncomingPacketFactory<RequestPushNotify>("MessageSvc.PushNotify") {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): RequestPushNotify {
discardExact(8)
return decodeUniPacket(RequestPushNotify.serializer())
}
override suspend fun QQAndroidBot.handle(packet: RequestPushNotify) {
override suspend fun QQAndroidBot.handle(packet: RequestPushNotify, sequenceId: Int): OutgoingPacket? {
network.run {
PbGetMsg(client, MsgSvc.SyncFlag.START, packet.stMsgInfo?.uMsgTime ?: 0).sendAndExpect<MultiPacket<FriendMessage>>()
return PbGetMsg(client, MsgSvc.SyncFlag.START, packet.stMsgInfo?.uMsgTime ?: 0)
}
}
}
@ -56,7 +53,7 @@ internal class MessageSvc {
* 获取好友消息和消息记录
*/
@UseExperimental(MiraiInternalAPI::class)
internal object PbGetMsg : PacketFactory<PbGetMsg.Response>("MessageSvc.PbGetMsg") {
internal object PbGetMsg : OutgoingPacketFactory<PbGetMsg.Response>("MessageSvc.PbGetMsg") {
val EXTRA_DATA =
"08 00 12 33 6D 6F 64 65 6C 3A 78 69 67 6F 6D 69 20 36 3B 6F 73 3A 32 32 3B 76 65 72 73 69 6F 6E 3A 76 32 6D 61 6E 3A 78 69 61 6F 6D 69 73 79 73 3A 4C 4D 59 34 38 5A 18 E4 E1 A4 FF FE 2D 20 E9 E1 A4 FF FE 2D 28 A8 E1 A4 FF FE 2D 30 99 E1 A4 FF FE 2D".hexToBytes()
@ -81,7 +78,7 @@ internal class MessageSvc {
syncFlag = syncFlag,
// serverBuf = from.serverBuf ?: EMPTY_BYTE_ARRAY,
syncCookie = client.c2cMessageSync.syncCookie
?: SyncCookie(time = Random.nextLong()).toByteArray(SyncCookie.serializer())//.also { client.c2cMessageSync.syncCookie = it },
?: SyncCookie(time = msgTime + client.timeDifference).toByteArray(SyncCookie.serializer())//.also { client.c2cMessageSync.syncCookie = it },
// syncFlag = client.c2cMessageSync.syncFlag,
//msgCtrlBuf = client.c2cMessageSync.msgCtrlBuf,
//pubaccountCookie = client.c2cMessageSync.pubAccountCookie
@ -160,7 +157,7 @@ internal class MessageSvc {
/**
* 被挤下线
*/
internal object PushForceOffline : PacketFactory<ForceOfflineEvent>("MessageSvc.PushForceOffline") {
internal object PushForceOffline : OutgoingPacketFactory<ForceOfflineEvent>("MessageSvc.PushForceOffline") {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): ForceOfflineEvent {
discardExact(4)
val struct = this.decodeUniPacket(RequestPushForceOffline.serializer())
@ -168,7 +165,7 @@ internal class MessageSvc {
}
}
internal object PbSendMsg : PacketFactory<PbSendMsg.Response>("MessageSvc.PbSendMsg") {
internal object PbSendMsg : OutgoingPacketFactory<PbSendMsg.Response>("MessageSvc.PbSendMsg") {
sealed class Response : Packet {
object SUCCESS : Response() {
override fun toString(): String = "MessageSvc.PbSendMsg.Response.SUCCESS"
@ -230,9 +227,9 @@ internal class MessageSvc {
elems = message.toRichTextElems()
)
),
msgSeq = client.atomicNextMessageSequenceId()
// msgRand = 123
//syncCookie = client.c2cMessageSync.syncCookie?.takeIf { it.isNotEmpty() } ?:
msgSeq = client.atomicNextMessageSequenceId(),
//msgRand = Random.nextInt() and 0x7FFF,
syncCookie = client.c2cMessageSync.syncCookie?.takeIf { it.isNotEmpty() } ?: EMPTY_BYTE_ARRAY
//SyncCookie(currentTimeSeconds, Random.nextLong().absoluteValue, Random.nextLong().absoluteValue).toByteArray(SyncCookie.serializer())
// msgVia = 1
)

View File

@ -11,7 +11,7 @@ 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
import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory
import net.mamoe.mirai.qqandroid.utils.toMessageChain
@ -19,9 +19,9 @@ internal class OnlinePush {
/**
* 接受群消息
*/
internal object PbPushGroupMsg : PacketFactory<GroupMessage>("OnlinePush.PbPushGroupMsg") {
internal object PbPushGroupMsg : IncomingPacketFactory<GroupMessage>("OnlinePush.PbPushGroupMsg") {
@UseExperimental(ExperimentalStdlibApi::class)
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): GroupMessage {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): 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 = ProtoBufWithNullableSupport.load(MsgOnlinePush.PbPushMsg.serializer(), readBytes())
@ -48,9 +48,5 @@ internal class OnlinePush {
}
)
}
override suspend fun QQAndroidBot.handle(packet: GroupMessage) {
}
}
}

View File

@ -14,7 +14,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.jce.*
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Vec0xd50
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
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.OutgoingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
import net.mamoe.mirai.utils.io.discardExact
@ -22,7 +22,7 @@ import net.mamoe.mirai.utils.io.discardExact
internal class FriendList {
internal object GetTroopMemberList :
PacketFactory<GetTroopMemberList.Response>("friendlist.GetTroopMemberListReq") {
OutgoingPacketFactory<GetTroopMemberList.Response>("friendlist.GetTroopMemberListReq") {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): GetTroopMemberList.Response {
TODO()
}
@ -65,7 +65,7 @@ internal class FriendList {
}
internal object GetTroopListSimplify :
PacketFactory<GetTroopListSimplify.Response>("friendlist.GetTroopListReqV2") {
OutgoingPacketFactory<GetTroopListSimplify.Response>("friendlist.GetTroopListReqV2") {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): GetTroopListSimplify.Response {
val res = this.decodeUniPacket(GetTroopListRespV2.serializer())
return Response(res.vecTroopList.orEmpty())
@ -109,7 +109,8 @@ internal class FriendList {
}
}
}
internal object GetFriendGroupList : PacketFactory<GetFriendGroupList.Response>("friendlist.getFriendGroupList") {
internal object GetFriendGroupList : OutgoingPacketFactory<GetFriendGroupList.Response>("friendlist.getFriendGroupList") {
class Response(
val totalFriendCount: Short,

View File

@ -9,23 +9,25 @@ import net.mamoe.mirai.qqandroid.io.serialization.jceRequestSBuffer
import net.mamoe.mirai.qqandroid.io.serialization.writeJceStruct
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.PushResp
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
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.IncomingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildResponseUniPacket
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.PushReq as PushReqJceStruct
internal class ConfigPushSvc {
object PushReq : PacketFactory<PushReqJceStruct>("ConfigPushSvc.PushReq") {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): PushReqJceStruct {
object PushReq : IncomingPacketFactory<PushReqJceStruct>(
receivingCommandName = "ConfigPushSvc.PushReq",
responseCommandName = "ConfigPushSvc.PushResp"
) {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): PushReqJceStruct {
discardExact(4)
val pushReq = decodeUniPacket(PushReqJceStruct.serializer())
//println(pushReq.contentToString())
return pushReq
return decodeUniPacket(PushReqJceStruct.serializer())
}
override suspend fun QQAndroidBot.handle(packet: PushReqJceStruct) {
network.run {
buildOutgoingUniPacket(
override suspend fun QQAndroidBot.handle(packet: PushReqJceStruct, sequenceId: Int): OutgoingPacket? {
return network.run {
buildResponseUniPacket(
client,
sequenceId = client.configPushSvcPushReqSequenceId,
commandName = "ConfigPushSvc.PushResp",
@ -51,7 +53,7 @@ internal class ConfigPushSvc {
charset = JceCharset.UTF8
)
// writePacket(this.build().debugPrintThis())
}.sendWithoutExpect()
}
}
}
}

View File

@ -11,16 +11,19 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.*
import net.mamoe.mirai.qqandroid.utils.GuidSource
import net.mamoe.mirai.qqandroid.utils.MacOrAndroidIdChangeFlag
import net.mamoe.mirai.qqandroid.utils.guidFlag
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.MiraiDebugAPI
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.cryptor.decryptBy
import net.mamoe.mirai.utils.currentTimeSeconds
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.io.discardExact
import net.mamoe.mirai.utils.md5
/**
* OicqRequest
*/
@UseExperimental(ExperimentalUnsignedTypes::class)
internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("wtlogin.login") {
internal object LoginPacket : OutgoingPacketFactory<LoginPacket.LoginPacketResponse>("wtlogin.login") {
/**
* 提交验证码
@ -686,7 +689,7 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("wt
*/
private fun QQAndroidClient.analysisTlv130(t130: ByteArray) = t130.read {
discardExact(2)
timeDifference = readUInt().toLong() - currentTimeMillis
timeDifference = readUInt().toLong() - currentTimeSeconds
ipFromT149 = readBytes(4)
}

View File

@ -10,7 +10,7 @@ import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.SvcReqRegister
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.OutgoingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildLoginOutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.oidb.oidb0x769.Oidb0x769
import net.mamoe.mirai.qqandroid.network.protocol.packet.writeSsoPacket
@ -33,7 +33,7 @@ internal enum class RegPushReason {
}
internal class StatSvc {
internal object Register : PacketFactory<Register.Response>("StatSvc.register") {
internal object Register : OutgoingPacketFactory<Register.Response>("StatSvc.register") {
internal object Response : Packet {
override fun toString(): String = "Response(StatSvc.register)"

View File

@ -7,7 +7,7 @@ import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.packet.*
internal object TransEmpPacket : PacketFactory<TransEmpPacket.Response>("wtlogin.trans_emp") {
internal object TransEmpPacket : OutgoingPacketFactory<TransEmpPacket.Response>("wtlogin.trans_emp") {
private const val appId = 16L
private const val subAppId = 537062845L

View File

@ -4,10 +4,7 @@ package androidPacketTests
import kotlinx.io.core.*
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.qqandroid.network.protocol.packet.DECRYPTER_16_ZERO
import net.mamoe.mirai.qqandroid.network.protocol.packet.KnownPacketFactories
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger
import net.mamoe.mirai.qqandroid.network.protocol.packet.withUse
import net.mamoe.mirai.qqandroid.network.protocol.packet.*
import net.mamoe.mirai.utils.cryptor.ECDH
import net.mamoe.mirai.utils.cryptor.adjustToPublicKey
import net.mamoe.mirai.utils.cryptor.decryptBy
@ -99,7 +96,7 @@ private fun processFullPacketWithoutLength(packet: ByteReadPacket) {
try {
bytes.toReadPacket().parseOicqResponse {
debugIfFail {
if (it.packetFactory.commandName == "wtlogin.login") {
if ((it.packetFactory as? OutgoingPacketFactory<*>)?.commandName == "wtlogin.login") {
DebugLogger.info("服务器发来了 wtlogin.login. 正在解析 key")
try {
val subCommand = readUShort().toInt()

View File

@ -5,59 +5,73 @@ import kotlin.jvm.JvmOverloads
/**
* 用于创建默认的日志记录器. 在一些需要使用日志的 Mirai 的组件, [Bot], 都会通过这个函数构造日志记录器
* 用于创建默认的日志记录器. 在一些需要使用日志的 Mirai 的组件, [Bot], 都会通过这个函数构造日志记录器.
* 可直接修改这个变量的值来重定向日志输出.
*
* **注意:** 请务必将所有的输出定向到日志记录系统, 否则在某些情况下 ( web 控制台中) 将无法接收到输出
*
* **注意:** 请为日志做好分类, 即不同的模块使用不同的 [MiraiLogger].
* , [Bot] 中使用 identity "Bot(qqId)" [MiraiLogger]
* [Bot] 的网络处理中使用 identity "BotNetworkHandler" .
*/
var DefaultLogger: (identity: String?) -> MiraiLogger = { PlatformLogger(it) }
/**
* 当前平台的默认的日志记录器.
* _JVM 控制台_ 端的实现为 [println]
* _Android_ 端的实现为 [android.util.Log]
*
* 不应该直接构造这个类的实例. 请使用 [DefaultLogger], 或使用默认的顶层日志记录 [MiraiLogger.Companion]
*/
expect open class PlatformLogger @JvmOverloads internal constructor(identity: String? = "Mirai") : MiraiLoggerPlatformBase
/**
* 给这个 logger 添加一个开关, 用于控制是否记录 log
*
*/
@JvmOverloads
fun MiraiLogger.withSwitch(default: Boolean = true): MiraiLoggerWithSwitch = MiraiLoggerWithSwitch(this, default)
/**
* 日志记录器. 所有的输出均依赖于它.
* 不同的对象可拥有只属于自己的 logger. 通过 [identity] 来区分.
* 不同的对象可拥有只属于自己的 logger. 通过 [identity] 来区分.
*
* 注意: 请不要直接实现这个接口, 请继承 [MiraiLoggerPlatformBase]
* 注意: 如果你需要重新实现日志, 请不要直接实现这个接口, 请继承 [MiraiLoggerPlatformBase]
*
* @see MiraiLoggerPlatformBase 平台通用基础实现
* 在定义 logger 变量时, 请一直使用 [MiraiLogger] 或者 [MiraiLoggerWithSwitch].
*
* @see SimpleLogger 简易 logger, 它将所有的日志记录操作都转移给 lambda `(String?, Throwable?) -> Unit`
* @see PlatformLogger 各个平台下的默认日志记录实现.
* @see SilentLogger 忽略任何日志记录操作的 logger 实例.
*
* @see MiraiLoggerPlatformBase 平台通用基础实现.
*/
interface MiraiLogger {
/**
* 顶层日志记录器.
*
* 顶层日志会导致混乱并难以定位问题. 请自行构造 logger 实例并使用.
* 请参考使用 [DefaultLogger]
*/
@Deprecated(message = "顶层日志会导致混乱并难以定位问题. 请自行构造 logger 实例并使用.", level = DeprecationLevel.WARNING)
companion object : MiraiLogger by DefaultLogger("Mirai")
/**
* 日志的标记. Mirai , identity 可为
* - "Bot"
* - "BotNetworkHandler"
* .
*
* 它只用于帮助调试或统计. 十分建议清晰定义 identity
*/
val identity: String?
/**
* 随从. this 中调用所有方法后都应继续往 [follower] 传递调用.
* [follower] 的存在可以让一次日志被多个日志记录器记录.
*
* :
* ```kotlin
* val bot = Bot( ... )
* bot.follower = MyOwnLogger()
* 一般不建议直接修改这个属性. 请通过 [plus] 来连接两个日志记录器.
* : `val logger = bot.logger + MyOwnLogger()`
* 这样, 当调用 `logger.info()` , bot.logger 会首先记录, MyOwnLogger 会随后记录.
*
* bot.info("Hi")
* ```
* 在这个例子中的 `MyOwnLogger` 将可以记录到 "Hi".
* 当然, 多个 logger 也可以加在一起: `val logger = bot.logger + MyOwnLogger() + MyOwnLogger2()`
*/
var follower: MiraiLogger?
/**
* 记录一个 `verbose` 级别的日志.
* 无关紧要的, 经常大量输出的日志应使用它.
*/
fun verbose(any: Any?)
@ -65,7 +79,7 @@ interface MiraiLogger {
fun verbose(message: String?, e: Throwable?)
/**
* 记录一个 `debug` 级别的日志.
* 记录一个 _调试_ 级别的日志.
*/
fun debug(any: Any?)
@ -74,7 +88,7 @@ interface MiraiLogger {
/**
* 记录一个 `info` 级别的日志.
* 记录一个 _信息_ 级别的日志.
*/
fun info(any: Any?)
@ -83,7 +97,7 @@ interface MiraiLogger {
/**
* 记录一个 `warning` 级别的日志.
* 记录一个 _警告_ 级别的日志.
*/
fun warning(any: Any?)
@ -92,7 +106,7 @@ interface MiraiLogger {
/**
* 记录一个 `error` 级别的日志.
* 记录一个 _错误_ 级别的日志.
*/
fun error(e: Any?)
@ -109,10 +123,9 @@ interface MiraiLogger {
* | base | <-- | follower | <-- | follower | <-- | follower |
* +------+ +----------+ +----------+ +----------+
*
* @see follower
* @return [follower]
*/
operator fun plus(follower: MiraiLogger): MiraiLogger
operator fun <T : MiraiLogger> plus(follower: T): T
/**
* 添加一个 [follower]
@ -123,6 +136,15 @@ interface MiraiLogger {
operator fun plusAssign(follower: MiraiLogger)
}
/**
* 当前平台的默认的日志记录器.
* _JVM 控制台_ 端的实现为 [println]
* _Android_ 端的实现为 [android.util.Log]
*
* 不应该直接构造这个类的实例. 请使用 [DefaultLogger], 或使用默认的顶层日志记录 [MiraiLogger.Companion]
*/
expect open class PlatformLogger @JvmOverloads internal constructor(identity: String? = "Mirai") : MiraiLoggerPlatformBase
/**
* 不做任何事情的 logger, keep silent.
*/
@ -167,7 +189,11 @@ class SimpleLogger(override val identity: String?, private val logger: (String?,
class MiraiLoggerWithSwitch internal constructor(private val delegate: MiraiLogger, default: Boolean) : MiraiLoggerPlatformBase() {
override val identity: String? get() = delegate.identity
private var switch: Boolean = default
/**
* true 为开启.
*/
@PublishedApi
internal var switch: Boolean = default
fun enable() {
switch = true
@ -188,15 +214,53 @@ class MiraiLoggerWithSwitch internal constructor(private val delegate: MiraiLogg
override fun error0(any: Any?) = if (switch) delegate.error(any) else Unit
override fun error0(message: String?, e: Throwable?) = if (switch) delegate.error(message, e) else Unit
inline fun verbose(lazyMessage: () -> String) {
if (switch) verbose(lazyMessage())
}
inline fun verbose(lazyMessage: () -> String, e: Throwable?) {
if (switch) verbose(lazyMessage(), e)
}
inline fun debug(lazyMessage: () -> Any?) {
if (switch) debug(lazyMessage())
}
inline fun debug(lazyMessage: () -> String?, e: Throwable?) {
if (switch) debug(lazyMessage(), e)
}
inline fun info(lazyMessage: () -> Any?) {
if (switch) info(lazyMessage())
}
inline fun info(lazyMessage: () -> String?, e: Throwable?) {
if (switch) info(lazyMessage(), e)
}
inline fun warning(lazyMessage: () -> Any?) {
if (switch) warning(lazyMessage())
}
inline fun warning(lazyMessage: () -> String?, e: Throwable?) {
if (switch) warning(lazyMessage(), e)
}
inline fun error(lazyMessage: () -> Any?) {
if (switch) error(lazyMessage())
}
inline fun error(lazyMessage: () -> String?, e: Throwable?) {
if (switch) error(lazyMessage(), e)
}
}
/**
* 平台日志基类.
* 实现了 [follower] 的调用传递.
* 日志基类. 实现了 [follower] 的调用传递.
* Mirai 自带的日志系统无法满足需求, 请继承这个类并实现其抽象函数.
*
* 若要自行实现日志记录, 请优先考虑继承 [PlatformLogger].
*
* 它不应该被用作变量的类型定义. 只应被继承
* 这个类不应该被用作变量的类型定义. 只应被作为继承对象.
* 在定义 logger 变量时, 请一直使用 [MiraiLogger] 或者 [MiraiLoggerWithSwitch].
*/
abstract class MiraiLoggerPlatformBase : MiraiLogger {
final override var follower: MiraiLogger? = null
@ -262,7 +326,7 @@ abstract class MiraiLoggerPlatformBase : MiraiLogger {
protected abstract fun error0(any: Any?)
protected abstract fun error0(message: String?, e: Throwable?)
override fun plus(follower: MiraiLogger): MiraiLogger {
override operator fun <T : MiraiLogger> plus(follower: T): T {
this.follower = follower
return follower
}