Browse Source

Redesign events

Him188 6 years ago
parent
commit
2b477b3be7
58 changed files with 1154 additions and 1198 deletions
  1. 2 0
      mirai-core/build.gradle.kts
  2. 1 1
      mirai-core/src/androidMain/kotlin/net/mamoe/mirai/network/protocol/tim/NetworkDispatcherAndroid.kt
  3. 11 11
      mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/PlatformLoggerAndroid.kt
  4. 24 3
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt
  5. 33 98
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt
  6. 11 4
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribable.kt
  7. 31 25
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribers.kt
  8. 10 10
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt
  9. 0 42
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/FriendEvents.kt
  10. 0 52
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/GroupEvents.kt
  11. 1 2
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/PacketEvents.kt
  12. 12 12
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt
  13. 17 9
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Message.kt
  14. 6 5
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/internal/MessageDataInternal.kt
  15. 44 27
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/TIMBotNetworkHandler.kt
  16. 0 69
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/handler/EventPacketHandler.kt
  17. 3 3
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/Annotations.kt
  18. 1 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/Decrypters.kt
  19. 8 9
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/OutgoingPacket.kt
  20. 4 305
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/Packet.kt
  21. 73 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/PacketFactory.kt
  22. 82 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/PacketId.kt
  23. 0 32
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/ServerPacket.kt
  24. 0 42
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/UnknownServerPacket.kt
  25. 5 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/AddContact.kt
  26. 0 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/GradeInfo.kt
  27. 92 76
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/UploadImage.kt
  28. 33 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/AndroidOnlineStatusChange.kt
  29. 6 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/EventPacket.kt
  30. 106 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/EventPacketFactory.kt
  31. 23 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/FriendConversationIniliaze.kt
  32. 17 18
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/FriendOnlineStatusChanged.kt
  33. 22 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/GroupFileUpload.kt
  34. 18 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/Ignored.kt
  35. 46 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/MemberPermission.kt
  36. 173 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/Message.kt
  37. 0 237
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/ServerEventPackets.kt
  38. 34 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/Unknown.kt
  39. 2 2
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Captcha.kt
  40. 3 1
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/ChangeOnlineStatusPacket.kt
  41. 0 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Heartbeat.kt
  42. 86 39
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/PasswordSubmission.kt
  43. 2 2
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Touch.kt
  44. 6 3
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Calculation.kt
  45. 3 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt
  46. 2 2
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/MiraiLogger.kt
  47. 0 2
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/PlatformLogger.kt
  48. 3 2
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/SuspendLazy.kt
  49. 51 14
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/TEA.kt
  50. 2 2
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformSocket.kt
  51. 2 1
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/TypeConvertion.kt
  52. 1 1
      mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/event/SubscribersJvm.kt
  53. 1 2
      mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/network/protocol/tim/NetworkDispatcherJvm.kt
  54. 18 3
      mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/DefaultCaptchaSolverJvm.kt
  55. 13 22
      mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/MiraiLoggerJvm.kt
  56. 7 6
      mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt
  57. 1 0
      mirai-demos/mirai-demo-gentleman/build.gradle
  58. 2 2
      mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Main.kt

+ 2 - 0
mirai-core/build.gradle.kts

@@ -68,6 +68,8 @@ kotlin {
             dependsOn(commonMain)
             implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
 
+            implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")
+
             implementation("io.ktor:ktor-client-android:$ktorVersion")
 
         }

+ 1 - 1
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/network/protocol/tim/NetworkDispatcherAndroid.kt

@@ -9,4 +9,4 @@ import java.util.concurrent.Executors
  *
  * JVM: 独立的 4 thread 调度器
  */
-actual val NetworkDispatcher: CoroutineDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher()
+internal actual val NetworkDispatcher: CoroutineDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher()

+ 11 - 11
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/PlatformLoggerAndroid.kt

@@ -8,44 +8,44 @@ actual typealias PlatformLogger = AndroidLogger
  * Android 平台的默认的日志记录器, 使用 [Log]
  * 不应该直接构造这个类的实例. 需使用 [DefaultLogger]
  */
-open class AndroidLogger(override val identity: String?) : MiraiLoggerPlatformBase() {
+open class AndroidLogger(override val identity: String? = "Mirai") : MiraiLoggerPlatformBase() {
     override fun verbose0(any: Any?) {
-        Log.v(identity, any.toString())
+        Log.v(identity, any?.toString() ?: "")
     }
 
     override fun verbose0(message: String?, e: Throwable?) {
-        Log.v(identity, message.toString(), e)
+        Log.v(identity, message ?: "", e)
     }
 
     override fun debug0(any: Any?) {
-        Log.d(identity, any.toString())
+        Log.d(identity, any?.toString() ?: "")
     }
 
     override fun debug0(message: String?, e: Throwable?) {
-        Log.d(identity, message.toString(), e)
+        Log.d(identity, message ?: "", e)
     }
 
     override fun info0(any: Any?) {
-        Log.i(identity, any.toString())
+        Log.i(identity, any?.toString() ?: "")
     }
 
     override fun info0(message: String?, e: Throwable?) {
-        Log.i(identity, message.toString(), e)
+        Log.i(identity, message ?: "", e)
     }
 
     override fun warning0(any: Any?) {
-        Log.w(identity, any.toString())
+        Log.w(identity, any?.toString() ?: "")
     }
 
     override fun warning0(message: String?, e: Throwable?) {
-        Log.w(identity, message.toString(), e)
+        Log.w(identity, message ?: "", e)
     }
 
     override fun error0(any: Any?) {
-        Log.e(identity, any.toString())
+        Log.e(identity, any?.toString() ?: "")
     }
 
     override fun error0(message: String?, e: Throwable?) {
-        Log.e(identity, message.toString(), e)
+        Log.e(identity, message ?: "", e)
     }
 }

+ 24 - 3
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt

@@ -9,10 +9,13 @@ import net.mamoe.mirai.message.Message
 import net.mamoe.mirai.message.MessageChain
 import net.mamoe.mirai.message.singleChain
 import net.mamoe.mirai.network.BotSession
-import net.mamoe.mirai.network.protocol.tim.handler.EventPacketHandler
 import net.mamoe.mirai.network.protocol.tim.packet.action.RequestProfileDetailsPacket
 import net.mamoe.mirai.network.protocol.tim.packet.action.RequestProfileDetailsResponse
+import net.mamoe.mirai.network.protocol.tim.packet.action.SendFriendMessagePacket
+import net.mamoe.mirai.network.protocol.tim.packet.action.SendGroupMessagePacket
+import net.mamoe.mirai.network.sessionKey
 import net.mamoe.mirai.qqAccount
+import net.mamoe.mirai.sendPacket
 import net.mamoe.mirai.utils.SuspendLazy
 import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
 import net.mamoe.mirai.withSession
@@ -89,7 +92,7 @@ class Group internal constructor(bot: Bot, val groupId: GroupId) : Contact(bot,
         get() = TODO("Implementing group members is less important")
 
     override suspend fun sendMessage(message: MessageChain) {
-        bot.network[EventPacketHandler].sendGroupMessage(this, message)
+        bot.sendPacket(SendGroupMessagePacket(bot.qqAccount, internalId, bot.sessionKey, message))
     }
 
     companion object
@@ -114,7 +117,7 @@ open class QQ internal constructor(bot: Bot, id: UInt) : Contact(bot, id) {
     val profile: Deferred<Profile> by bot.network.SuspendLazy { updateProfile() }
 
     override suspend fun sendMessage(message: MessageChain) {
-        bot.network[EventPacketHandler].sendFriendMessage(this, message)
+        bot.sendPacket(SendFriendMessagePacket(bot.qqAccount, id, bot.sessionKey, message))
     }
 
     /**
@@ -154,6 +157,24 @@ class Member internal constructor(bot: Bot, id: UInt, val group: Group) : QQ(bot
     }
 }
 
+/**
+ * 群成员的权限
+ */
+enum class MemberPermission {
+    /**
+     * 群主
+     */
+    OWNER,
+    /**
+     * 管理员
+     */
+    OPERATOR,
+    /**
+     * 一般群成员
+     */
+    MEMBER;
+}
+
 /**
  * 个人资料
  */

+ 33 - 98
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt

@@ -3,83 +3,21 @@
 package net.mamoe.mirai.event
 
 import net.mamoe.mirai.Bot
-import net.mamoe.mirai.contact.Contact
-import net.mamoe.mirai.contact.Group
-import net.mamoe.mirai.contact.QQ
-import net.mamoe.mirai.event.events.BotEvent
-import net.mamoe.mirai.event.events.FriendMessageEvent
-import net.mamoe.mirai.event.events.GroupMessageEvent
-import net.mamoe.mirai.message.*
-import net.mamoe.mirai.utils.ExternalImage
-import net.mamoe.mirai.utils.sendTo
-import net.mamoe.mirai.utils.upload
+import net.mamoe.mirai.message.Message
+import net.mamoe.mirai.message.any
+import net.mamoe.mirai.network.protocol.tim.packet.event.FriendMessage
+import net.mamoe.mirai.network.protocol.tim.packet.event.GroupMessage
+import net.mamoe.mirai.network.protocol.tim.packet.event.MessageEventPacket
 import kotlin.jvm.JvmName
 
-/**
- * 消息事件时创建的临时容器.
- */
-abstract class SenderAndMessage<TContact : Contact>(
-    /**
-     * 发送这条消息的用户.
-     */
-    val sender: QQ,
-    /**
-     * 消息事件主体. 对于好友消息, 这个属性为 [QQ] 的实例;  对于群消息, 这个属性为 [Group] 的实例
-     */
-    val subject: TContact,
-    val message: MessageChain
-) {
-    /**
-     * 给这个消息事件的主体发送消息
-     * 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息
-     * 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息
-     */
-    suspend inline fun reply(message: MessageChain) = subject.sendMessage(message)
-
-    suspend fun reply(message: Message) = subject.sendMessage(message.singleChain())
-    suspend fun reply(plain: String) = subject.sendMessage(plain.toMessage())
-
-
-    // region Send to subject
-    suspend inline fun ExternalImage.send() = this.sendTo(subject)
-
-    suspend inline fun ExternalImage.upload(): Image = this.upload(subject)
-    suspend inline fun Image.send() = this.sendTo(subject)
-    suspend inline fun ImageId.send() = this.sendTo(subject)
-    suspend inline fun Message.send() = this.sendTo(subject)
-    suspend inline fun String.send() = this.toMessage().sendTo(subject)
-
-    // endregion
-}
-
-/**
- * [subject] = [sender] = [QQ]
- */
-class FriendSenderAndMessage(
-    sender: QQ,
-    message: MessageChain
-) : SenderAndMessage<QQ>(sender, sender, message)
-
-/**
- * [subject] = [group] = [Group]
- */
-class GroupSenderAndMessage(
-    val group: Group,
-    sender: QQ,
-    message: MessageChain
-) : SenderAndMessage<Group>(sender, group, message)
-
 /**
  * 订阅来自所有 [Bot] 的所有联系人的消息事件. 联系人可以是任意群或任意好友或临时会话.
  */
 @MessageDsl
-suspend inline fun subscribeMessages(crossinline listeners: suspend MessageSubscribersBuilder<SenderAndMessage<*>>.() -> Unit) {
-    MessageSubscribersBuilder<SenderAndMessage<*>> { listener ->
-        subscribeAlways<BotEvent> {
-            when (it) {
-                is FriendMessageEvent -> listener(FriendSenderAndMessage(it.sender, it.message))
-                is GroupMessageEvent -> listener(GroupSenderAndMessage(it.group, it.sender, it.message))
-            }
+suspend inline fun subscribeMessages(crossinline listeners: suspend MessageSubscribersBuilder<MessageEventPacket<*>>.() -> Unit) {
+    MessageSubscribersBuilder<MessageEventPacket<*>> { listener ->
+        subscribeAlways<MessageEventPacket<*>> {
+            listener(it)
         }
     }.apply { listeners() }
 }
@@ -88,10 +26,10 @@ suspend inline fun subscribeMessages(crossinline listeners: suspend MessageSubsc
  * 订阅来自所有 [Bot] 的所有群消息事件
  */
 @MessageDsl
-suspend inline fun subscribeGroupMessages(crossinline listeners: suspend MessageSubscribersBuilder<GroupSenderAndMessage>.() -> Unit) {
-    MessageSubscribersBuilder<GroupSenderAndMessage> { listener ->
-        subscribeAlways<GroupMessageEvent> {
-            listener(GroupSenderAndMessage(it.group, it.sender, it.message))
+suspend inline fun subscribeGroupMessages(crossinline listeners: suspend MessageSubscribersBuilder<GroupMessage>.() -> Unit) {
+    MessageSubscribersBuilder<GroupMessage> { listener ->
+        subscribeAlways<GroupMessage> {
+            listener(it)
         }
     }.apply { listeners() }
 }
@@ -100,10 +38,10 @@ suspend inline fun subscribeGroupMessages(crossinline listeners: suspend Message
  * 订阅来自所有 [Bot] 的所有好友消息事件
  */
 @MessageDsl
-suspend inline fun subscribeFriendMessages(crossinline listeners: suspend MessageSubscribersBuilder<FriendSenderAndMessage>.() -> Unit) {
-    MessageSubscribersBuilder<FriendSenderAndMessage> { listener ->
-        subscribeAlways<FriendMessageEvent> {
-            listener(FriendSenderAndMessage(it.sender, it.message))
+suspend inline fun subscribeFriendMessages(crossinline listeners: suspend MessageSubscribersBuilder<FriendMessage>.() -> Unit) {
+    MessageSubscribersBuilder<FriendMessage> { listener ->
+        subscribeAlways<FriendMessage> {
+            listener(it)
         }
     }.apply { listeners() }
 }
@@ -112,13 +50,10 @@ suspend inline fun subscribeFriendMessages(crossinline listeners: suspend Messag
  * 订阅来自这个 [Bot] 的所有联系人的消息事件. 联系人可以是任意群或任意好友或临时会话.
  */
 @MessageDsl
-suspend inline fun Bot.subscribeMessages(crossinline listeners: suspend MessageSubscribersBuilder<SenderAndMessage<*>>.() -> Unit) {
-    MessageSubscribersBuilder<SenderAndMessage<*>> { listener ->
-        this.subscribeAlways<BotEvent> {
-            when (it) {
-                is FriendMessageEvent -> listener(FriendSenderAndMessage(it.sender, it.message))
-                is GroupMessageEvent -> listener(GroupSenderAndMessage(it.group, it.sender, it.message))
-            }
+suspend inline fun Bot.subscribeMessages(crossinline listeners: suspend MessageSubscribersBuilder<MessageEventPacket<*>>.() -> Unit) {
+    MessageSubscribersBuilder<MessageEventPacket<*>> { listener ->
+        this.subscribeAlways<MessageEventPacket<*>> {
+            listener(it)
         }
     }.apply { listeners() }
 }
@@ -127,10 +62,10 @@ suspend inline fun Bot.subscribeMessages(crossinline listeners: suspend MessageS
  * 订阅来自这个 [Bot] 的所有群消息事件
  */
 @MessageDsl
-suspend inline fun Bot.subscribeGroupMessages(crossinline listeners: suspend MessageSubscribersBuilder<GroupSenderAndMessage>.() -> Unit) {
-    MessageSubscribersBuilder<GroupSenderAndMessage> { listener ->
-        this.subscribeAlways<GroupMessageEvent> {
-            listener(GroupSenderAndMessage(it.group, it.sender, it.message))
+suspend inline fun Bot.subscribeGroupMessages(crossinline listeners: suspend MessageSubscribersBuilder<GroupMessage>.() -> Unit) {
+    MessageSubscribersBuilder<GroupMessage> { listener ->
+        this.subscribeAlways<GroupMessage> {
+            listener(it)
         }
     }.apply { listeners() }
 }
@@ -139,10 +74,10 @@ suspend inline fun Bot.subscribeGroupMessages(crossinline listeners: suspend Mes
  * 订阅来自这个 [Bot] 的所有好友消息事件.
  */
 @MessageDsl
-suspend inline fun Bot.subscribeFriendMessages(crossinline listeners: suspend MessageSubscribersBuilder<FriendSenderAndMessage>.() -> Unit) {
-    MessageSubscribersBuilder<FriendSenderAndMessage> { listener ->
-        this.subscribeAlways<FriendMessageEvent> {
-            listener(FriendSenderAndMessage(it.sender, it.message))
+suspend inline fun Bot.subscribeFriendMessages(crossinline listeners: suspend MessageSubscribersBuilder<FriendMessage>.() -> Unit) {
+    MessageSubscribersBuilder<FriendMessage> { listener ->
+        this.subscribeAlways<FriendMessage> {
+            listener(it)
         }
     }.apply { listeners() }
 }
@@ -151,11 +86,11 @@ internal typealias MessageReplier<T> = @MessageDsl suspend T.(String) -> Message
 
 internal typealias StringReplier<T> = @MessageDsl suspend T.(String) -> String
 
-internal suspend inline operator fun <T : SenderAndMessage<*>> (@MessageDsl suspend T.(String) -> Unit).invoke(t: T) =
+internal suspend inline operator fun <T : MessageEventPacket<*>> (@MessageDsl suspend T.(String) -> Unit).invoke(t: T) =
     this.invoke(t, t.message.stringValue)
 
 @JvmName("invoke1") //Avoid Platform declaration clash
-internal suspend inline operator fun <T : SenderAndMessage<*>> StringReplier<T>.invoke(t: T): String =
+internal suspend inline operator fun <T : MessageEventPacket<*>> StringReplier<T>.invoke(t: T): String =
     this.invoke(t, t.message.stringValue)
 
 /**
@@ -166,7 +101,7 @@ internal suspend inline operator fun <T : SenderAndMessage<*>> StringReplier<T>.
  */
 @Suppress("unused")
 @MessageDsl
-class MessageSubscribersBuilder<T : SenderAndMessage<*>>(
+class MessageSubscribersBuilder<T : MessageEventPacket<*>>(
     inline val subscriber: suspend (@MessageDsl suspend T.(String) -> Unit) -> Unit
 ) {
     /**
@@ -221,7 +156,7 @@ class MessageSubscribersBuilder<T : SenderAndMessage<*>>(
      * 如果是来自这个群的消息, 就执行 [onEvent]
      */
     suspend fun sentFrom(id: UInt, onEvent: @MessageDsl suspend T.(String) -> Unit) =
-        content({ if (this is GroupSenderAndMessage) group.id == id else false }, onEvent)
+        content({ if (this is GroupMessage) group.id == id else false }, onEvent)
 
     /**
      * 如果是来自这个群的消息, 就执行 [onEvent]

+ 11 - 4
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Event.kt → mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribable.kt

@@ -10,6 +10,13 @@ import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
 import kotlin.jvm.JvmOverloads
 
+/**
+ * 可被监听的.
+ *
+ * 可以是任何 class 或 object.
+ */
+interface Subscribable
+
 /**
  * 所有事件的基类.
  * 若监听这个类, 监听器将会接收所有事件的广播.
@@ -17,7 +24,7 @@ import kotlin.jvm.JvmOverloads
  * @see [broadcast] 广播事件
  * @see [subscribe] 监听事件
  */
-abstract class Event {
+abstract class Event : Subscribable {
 
     /**
      * 事件是否已取消. 事件需实现 [Cancellable] 才可以被取消, 否则这个字段为常量值 false
@@ -51,9 +58,9 @@ private val EventDebuggingFlag: Boolean by lazy {
 }
 
 /**
- * 实现这个接口的事件可以被取消.
+ * 实现这个接口的事件可以被取消. 在广播中取消不会影响广播过程.
  */
-interface Cancellable {
+interface Cancellable : Subscribable {
     val cancelled: Boolean
 
     fun cancel()
@@ -67,7 +74,7 @@ interface Cancellable {
  */
 @Suppress("UNCHECKED_CAST")
 @JvmOverloads
-suspend fun <E : Event> E.broadcast(context: CoroutineContext = EmptyCoroutineContext): E {
+suspend fun <E : Subscribable> E.broadcast(context: CoroutineContext = EmptyCoroutineContext): E {
     if (EventDebuggingFlag) {
         EventLogger.debug(this::class.simpleName + " pre broadcast")
     }

+ 31 - 25
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribers.kt

@@ -26,22 +26,26 @@ enum class ListeningStatus {
  * 订阅所有 [E] 及其子类事件.
  * 在
  */
-suspend inline fun <reified E : Event> subscribe(noinline handler: suspend (E) -> ListeningStatus) = E::class.subscribe(handler)
+suspend inline fun <reified E : Subscribable> subscribe(noinline handler: suspend (E) -> ListeningStatus) = E::class.subscribe(handler)
 
-suspend inline fun <reified E : Event> subscribeAlways(noinline listener: suspend (E) -> Unit) = E::class.subscribeAlways(listener)
+suspend inline fun <reified E : Subscribable> subscribeAlways(noinline listener: suspend (E) -> Unit) = E::class.subscribeAlways(listener)
 
-suspend inline fun <reified E : Event> subscribeOnce(noinline listener: suspend (E) -> Unit) = E::class.subscribeOnce(listener)
+suspend inline fun <reified E : Subscribable> subscribeOnce(noinline listener: suspend (E) -> Unit) = E::class.subscribeOnce(listener)
 
-suspend inline fun <reified E : Event, T> subscribeUntil(valueIfStop: T, noinline listener: suspend (E) -> T) = E::class.subscribeUntil(valueIfStop, listener)
-suspend inline fun <reified E : Event> subscribeUntilFalse(noinline listener: suspend (E) -> Boolean) = E::class.subscribeUntilFalse(listener)
-suspend inline fun <reified E : Event> subscribeUntilTrue(noinline listener: suspend (E) -> Boolean) = E::class.subscribeUntilTrue(listener)
-suspend inline fun <reified E : Event> subscribeUntilNull(noinline listener: suspend (E) -> Any?) = E::class.subscribeUntilNull(listener)
+suspend inline fun <reified E : Subscribable, T> subscribeUntil(valueIfStop: T, noinline listener: suspend (E) -> T) =
+    E::class.subscribeUntil(valueIfStop, listener)
 
+suspend inline fun <reified E : Subscribable> subscribeUntilFalse(noinline listener: suspend (E) -> Boolean) = E::class.subscribeUntilFalse(listener)
+suspend inline fun <reified E : Subscribable> subscribeUntilTrue(noinline listener: suspend (E) -> Boolean) = E::class.subscribeUntilTrue(listener)
+suspend inline fun <reified E : Subscribable> subscribeUntilNull(noinline listener: suspend (E) -> Any?) = E::class.subscribeUntilNull(listener)
 
-suspend inline fun <reified E : Event, T> subscribeWhile(valueIfContinue: T, noinline listener: suspend (E) -> T) = E::class.subscribeWhile(valueIfContinue, listener)
-suspend inline fun <reified E : Event> subscribeWhileFalse(noinline listener: suspend (E) -> Boolean) = E::class.subscribeWhileFalse(listener)
-suspend inline fun <reified E : Event> subscribeWhileTrue(noinline listener: suspend (E) -> Boolean) = E::class.subscribeWhileTrue(listener)
-suspend inline fun <reified E : Event> subscribeWhileNull(noinline listener: suspend (E) -> Any?) = E::class.subscribeWhileNull(listener)
+
+suspend inline fun <reified E : Subscribable, T> subscribeWhile(valueIfContinue: T, noinline listener: suspend (E) -> T) =
+    E::class.subscribeWhile(valueIfContinue, listener)
+
+suspend inline fun <reified E : Subscribable> subscribeWhileFalse(noinline listener: suspend (E) -> Boolean) = E::class.subscribeWhileFalse(listener)
+suspend inline fun <reified E : Subscribable> subscribeWhileTrue(noinline listener: suspend (E) -> Boolean) = E::class.subscribeWhileTrue(listener)
+suspend inline fun <reified E : Subscribable> subscribeWhileNull(noinline listener: suspend (E) -> Any?) = E::class.subscribeWhileNull(listener)
 
 // endregion
 
@@ -49,40 +53,42 @@ suspend inline fun <reified E : Event> subscribeWhileNull(noinline listener: sus
 // region KClass 的扩展方法 (不推荐)
 
 @PublishedApi
-internal suspend fun <E : Event> KClass<E>.subscribe(handler: suspend (E) -> ListeningStatus) = this.subscribeInternal(Handler(handler))
+internal suspend fun <E : Subscribable> KClass<E>.subscribe(handler: suspend (E) -> ListeningStatus) = this.subscribeInternal(Handler(handler))
 
 @PublishedApi
-internal suspend fun <E : Event> KClass<E>.subscribeAlways(listener: suspend (E) -> Unit) = this.subscribeInternal(Handler { listener(it); ListeningStatus.LISTENING })
+internal suspend fun <E : Subscribable> KClass<E>.subscribeAlways(listener: suspend (E) -> Unit) =
+    this.subscribeInternal(Handler { listener(it); ListeningStatus.LISTENING })
 
 @PublishedApi
-internal suspend fun <E : Event> KClass<E>.subscribeOnce(listener: suspend (E) -> Unit) = this.subscribeInternal(Handler { listener(it); ListeningStatus.STOPPED })
+internal suspend fun <E : Subscribable> KClass<E>.subscribeOnce(listener: suspend (E) -> Unit) =
+    this.subscribeInternal(Handler { listener(it); ListeningStatus.STOPPED })
 
 @PublishedApi
-internal suspend fun <E : Event, T> KClass<E>.subscribeUntil(valueIfStop: T, listener: suspend (E) -> T) =
+internal suspend fun <E : Subscribable, T> KClass<E>.subscribeUntil(valueIfStop: T, listener: suspend (E) -> T) =
     subscribeInternal(Handler { if (listener(it) === valueIfStop) ListeningStatus.STOPPED else ListeningStatus.LISTENING })
 
 @PublishedApi
-internal suspend fun <E : Event> KClass<E>.subscribeUntilFalse(listener: suspend (E) -> Boolean) = subscribeUntil(false, listener)
+internal suspend fun <E : Subscribable> KClass<E>.subscribeUntilFalse(listener: suspend (E) -> Boolean) = subscribeUntil(false, listener)
 
 @PublishedApi
-internal suspend fun <E : Event> KClass<E>.subscribeUntilTrue(listener: suspend (E) -> Boolean) = subscribeUntil(true, listener)
+internal suspend fun <E : Subscribable> KClass<E>.subscribeUntilTrue(listener: suspend (E) -> Boolean) = subscribeUntil(true, listener)
 
 @PublishedApi
-internal suspend fun <E : Event> KClass<E>.subscribeUntilNull(listener: suspend (E) -> Any?) = subscribeUntil(null, listener)
+internal suspend fun <E : Subscribable> KClass<E>.subscribeUntilNull(listener: suspend (E) -> Any?) = subscribeUntil(null, listener)
 
 
 @PublishedApi
-internal suspend fun <E : Event, T> KClass<E>.subscribeWhile(valueIfContinue: T, listener: suspend (E) -> T) =
+internal suspend fun <E : Subscribable, T> KClass<E>.subscribeWhile(valueIfContinue: T, listener: suspend (E) -> T) =
     subscribeInternal(Handler { if (listener(it) !== valueIfContinue) ListeningStatus.STOPPED else ListeningStatus.LISTENING })
 
 @PublishedApi
-internal suspend fun <E : Event> KClass<E>.subscribeWhileFalse(listener: suspend (E) -> Boolean) = subscribeWhile(false, listener)
+internal suspend fun <E : Subscribable> KClass<E>.subscribeWhileFalse(listener: suspend (E) -> Boolean) = subscribeWhile(false, listener)
 
 @PublishedApi
-internal suspend fun <E : Event> KClass<E>.subscribeWhileTrue(listener: suspend (E) -> Boolean) = subscribeWhile(true, listener)
+internal suspend fun <E : Subscribable> KClass<E>.subscribeWhileTrue(listener: suspend (E) -> Boolean) = subscribeWhile(true, listener)
 
 @PublishedApi
-internal suspend fun <E : Event> KClass<E>.subscribeWhileNull(listener: suspend (E) -> Any?) = subscribeWhile(null, listener)
+internal suspend fun <E : Subscribable> KClass<E>.subscribeWhileNull(listener: suspend (E) -> Any?) = subscribeWhile(null, listener)
 
 // endregion
 
@@ -94,7 +100,7 @@ internal suspend fun <E : Event> KClass<E>.subscribeWhileNull(listener: suspend
  */
 @ListenersBuilderDsl
 @PublishedApi
-internal suspend fun <E : Event> KClass<E>.subscribeAll(listeners: suspend ListenerBuilder<E>.() -> Unit) {
+internal suspend fun <E : Subscribable> KClass<E>.subscribeAll(listeners: suspend ListenerBuilder<E>.() -> Unit) {
     with(ListenerBuilder<E> { this.subscribeInternal(it) }) {
         listeners()
     }
@@ -105,7 +111,7 @@ internal suspend fun <E : Event> KClass<E>.subscribeAll(listeners: suspend Liste
  * @see ListenerBuilder
  */
 @ListenersBuilderDsl
-suspend inline fun <reified E : Event> subscribeAll(noinline listeners: suspend ListenerBuilder<E>.() -> Unit) = E::class.subscribeAll(listeners)
+suspend inline fun <reified E : Subscribable> subscribeAll(noinline listeners: suspend ListenerBuilder<E>.() -> Unit) = E::class.subscribeAll(listeners)
 
 /**
  * 监听构建器. 可同时进行多种方式的监听
@@ -125,7 +131,7 @@ suspend inline fun <reified E : Event> subscribeAll(noinline listeners: suspend
  */
 @ListenersBuilderDsl
 @Suppress("MemberVisibilityCanBePrivate", "unused")
-class ListenerBuilder<out E : Event>(
+class ListenerBuilder<out E : Subscribable>(
     @PublishedApi
     internal val handlerConsumer: suspend (Listener<E>) -> Unit
 ) {

+ 10 - 10
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt

@@ -1,18 +1,18 @@
 package net.mamoe.mirai.event.events
 
 import net.mamoe.mirai.Bot
-import net.mamoe.mirai.event.Cancellable
 import net.mamoe.mirai.event.Event
-import net.mamoe.mirai.message.ImageId
 
 
-abstract class BotEvent(val bot: Bot) : Event()
+abstract class BotEvent : Event {
+    private lateinit var _bot: Bot
+    open val bot: Bot get() = _bot
 
-class BotLoginSucceedEvent(bot: Bot) : BotEvent(bot)
+    constructor(bot: Bot) : super() {
+        this._bot = bot
+    }
 
-/**
- * 上传好友图片时, 服务器返回好友图片 ID 事件.
- *
- * 在这之后图片将会被上传到服务器.
- */
-class FriendImageIdObtainedEvent(bot: Bot, val imageId: ImageId) : BotEvent(bot), Cancellable
+    constructor() : super()
+}
+
+class BotLoginSucceedEvent(bot: Bot) : BotEvent(bot)

+ 0 - 42
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/FriendEvents.kt

@@ -1,42 +0,0 @@
-@file:Suppress("unused")
-
-package net.mamoe.mirai.event.events
-
-import net.mamoe.mirai.Bot
-import net.mamoe.mirai.contact.QQ
-import net.mamoe.mirai.message.Message
-import net.mamoe.mirai.message.MessageChain
-import net.mamoe.mirai.utils.OnlineStatus
-
-/**
- * 好友事件
- */
-sealed class FriendEvent(bot: Bot, val sender: QQ) : BotEvent(bot)
-
-/**
- * 接受好友消息事件
- *
- * @author Him188moe
- */
-class FriendMessageEvent(bot: Bot, sender: QQ, val message: MessageChain) : FriendEvent(bot, sender) {
-    suspend inline fun reply(message: Message) = sender.sendMessage(message)
-
-    suspend inline fun reply(message: String) = sender.sendMessage(message)
-
-    suspend inline fun reply(message: MessageChain) = sender.sendMessage(message)//shortcut
-}
-
-/**
- * 好友发起会话事件. 即好友在消息输入框内输入任意内容.
- */
-class FriendConversationInitializedEvent(bot: Bot, sender: QQ) : FriendEvent(bot, sender)
-
-/**
- * 好友在线状态改变事件
- */
-class FriendOnlineStatusChangedEvent(bot: Bot, sender: QQ, val newStatus: OnlineStatus) : FriendEvent(bot, sender)
-
-/**
- * 机器人账号被 [sender] 删除好友
- */
-class DeletedByFriendEvent(bot: Bot, qq: QQ) : FriendEvent(bot, qq)

+ 0 - 52
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/GroupEvents.kt

@@ -1,52 +0,0 @@
-@file:Suppress("unused")
-
-package net.mamoe.mirai.event.events
-
-import net.mamoe.mirai.Bot
-import net.mamoe.mirai.contact.Group
-import net.mamoe.mirai.contact.QQ
-import net.mamoe.mirai.message.Message
-import net.mamoe.mirai.message.MessageChain
-import net.mamoe.mirai.network.protocol.tim.packet.event.SenderPermission
-
-
-abstract class GroupEvent(bot: Bot, val group: Group) : BotEvent(bot)
-
-/**
- * 群消息
- */
-class GroupMessageEvent(
-    bot: Bot,
-    group: Group,
-    val sender: QQ,
-    val message: MessageChain,
-    val senderPermission: SenderPermission,
-    val senderName: String//若他有群名片就是群名片, 没有就是昵称
-) : GroupEvent(bot, group) {
-    suspend inline fun reply(message: Message) = group.sendMessage(message)
-
-    suspend inline fun reply(message: String) = group.sendMessage(message)
-
-    suspend inline fun reply(message: MessageChain) = group.sendMessage(message)
-}
-
-/**
- * 群成员权限改变
- */
-class MemberPermissionChangedEvent(
-    bot: Bot,
-    group: Group,
-    val member: QQ,
-    val kind: Kind
-) : GroupEvent(bot, group) {
-    enum class Kind {
-        /**
-         * 变成管理员
-         */
-        BECOME_OPERATOR,
-        /**
-         * 不再是管理员
-         */
-        NO_LONGER_OPERATOR,
-    } // TODO: 2019/11/2 变成群主的情况
-}

+ 1 - 2
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/PacketEvents.kt

@@ -47,5 +47,4 @@ sealed class ServerPacketEvent<P : Packet>(bot: Bot, packet: P) : PacketEvent<P>
 /**
  * 服务器数据包接收事件. 此时包已经解密完成.
  */
-class ServerPacketReceivedEvent<P : Packet>(bot: Bot, packet: P) : ServerPacketEvent<P>(bot, packet),
-    Cancellable
+class ServerPacketReceivedEvent<P : Packet>(bot: Bot, packet: P) : ServerPacketEvent<P>(bot, packet), Cancellable

+ 12 - 12
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt

@@ -5,10 +5,10 @@ import kotlinx.coroutines.launch
 import kotlinx.coroutines.sync.Mutex
 import kotlinx.coroutines.sync.withLock
 import net.mamoe.mirai.Bot
-import net.mamoe.mirai.event.Event
 import net.mamoe.mirai.event.EventLogger
 import net.mamoe.mirai.event.EventScope
 import net.mamoe.mirai.event.ListeningStatus
+import net.mamoe.mirai.event.Subscribable
 import net.mamoe.mirai.event.events.BotEvent
 import net.mamoe.mirai.utils.internal.inlinedRemoveIf
 import kotlin.jvm.JvmField
@@ -23,7 +23,7 @@ import kotlin.reflect.KFunction
  *
  * @author Him188moe
  */
-internal suspend fun <E : Event> KClass<E>.subscribeInternal(listener: Listener<E>): Unit = with(this.listeners()) {
+internal suspend fun <E : Subscribable> KClass<E>.subscribeInternal(listener: Listener<E>): Unit = with(this.listeners()) {
     if (mainMutex.tryLock(listener)) {//能锁则代表这个事件目前没有正在广播.
         try {
             add(listener)//直接修改主监听者列表
@@ -60,13 +60,13 @@ internal suspend fun <E : Event> KClass<E>.subscribeInternal(listener: Listener<
  *
  * @author Him188moe
  */
-sealed class Listener<in E : Event> {
+sealed class Listener<in E : Subscribable> {
     internal val lock = Mutex()
     abstract suspend fun onEvent(event: E): ListeningStatus
 }
 
 @PublishedApi
-internal class Handler<in E : Event>(@JvmField val handler: suspend (E) -> ListeningStatus) : Listener<E>() {
+internal class Handler<in E : Subscribable>(@JvmField val handler: suspend (E) -> ListeningStatus) : Listener<E>() {
     override suspend fun onEvent(event: E): ListeningStatus = handler.invoke(event)
 }
 
@@ -76,7 +76,7 @@ internal class Handler<in E : Event>(@JvmField val handler: suspend (E) -> Liste
  * 所有的 [BotEvent.bot] `!==` `bot` 的事件都不会被处理
  */
 @PublishedApi
-internal class HandlerWithBot<E : Event>(val bot: Bot, @JvmField val handler: suspend Bot.(E) -> ListeningStatus) :
+internal class HandlerWithBot<E : Subscribable>(val bot: Bot, @JvmField val handler: suspend Bot.(E) -> ListeningStatus) :
     Listener<E>() {
     override suspend fun onEvent(event: E): ListeningStatus = with(bot) {
         if (event !is BotEvent || event.bot !== this) {
@@ -94,9 +94,9 @@ internal class HandlerWithBot<E : Event>(val bot: Bot, @JvmField val handler: su
 /**
  * 这个事件类的监听器 list
  */
-internal suspend fun <E : Event> KClass<out E>.listeners(): EventListeners<E> = EventListenerManger.get(this)
+internal suspend fun <E : Subscribable> KClass<out E>.listeners(): EventListeners<E> = EventListenerManger.get(this)
 
-internal class EventListeners<E : Event> : MutableList<Listener<E>> by mutableListOf() {
+internal class EventListeners<E : Subscribable> : MutableList<Listener<E>> by mutableListOf() {
     /**
      * 主监听者列表.
      * 广播事件时使用这个锁.
@@ -125,11 +125,11 @@ internal class EventListeners<E : Event> : MutableList<Listener<E>> by mutableLi
  * [EventListeners] 是 lazy 的: 它们只会在被需要的时候才创建和存储.
  */
 internal object EventListenerManger {
-    private val registries: MutableMap<KClass<out Event>, EventListeners<*>> = mutableMapOf()
+    private val registries: MutableMap<KClass<out Subscribable>, EventListeners<*>> = mutableMapOf()
     private val registriesMutex = Mutex()
 
     @Suppress("UNCHECKED_CAST")
-    internal suspend fun <E : Event> get(clazz: KClass<out E>): EventListeners<E> = registriesMutex.withLock {
+    internal suspend fun <E : Subscribable> get(clazz: KClass<out E>): EventListeners<E> = registriesMutex.withLock {
         if (registries.containsKey(clazz)) {
             return registries[clazz] as EventListeners<E>
         } else {
@@ -141,7 +141,7 @@ internal object EventListenerManger {
     }
 }
 
-internal suspend fun <E : Event> E.broadcastInternal(): E {
+internal suspend fun <E : Subscribable> E.broadcastInternal(): E {
     suspend fun callListeners(listeners: EventListeners<in E>) {
         suspend fun callAndRemoveIfRequired() = listeners.inlinedRemoveIf {
             if (it.lock.tryLock()) {
@@ -177,12 +177,12 @@ internal suspend fun <E : Event> E.broadcastInternal(): E {
 /**
  * apply [block] to all the [EventListeners] in [clazz]'s superclasses
  */
-private tailrec suspend fun <E : Event> applySuperListeners(
+private tailrec suspend fun <E : Subscribable> applySuperListeners(
     clazz: KClass<out E>,
     block: suspend (EventListeners<in E>) -> Unit
 ) {
     val superEventClass =
-        clazz.supertypes.map { it.classifier }.filterIsInstance<KClass<out Event>>().firstOrNull() ?: return
+        clazz.supertypes.map { it.classifier }.filterIsInstance<KClass<out Subscribable>>().firstOrNull() ?: return
     @Suppress("UNCHECKED_CAST")
     block(superEventClass.listeners() as EventListeners<in E>)
     applySuperListeners(superEventClass, block)

+ 17 - 9
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Message.kt

@@ -6,6 +6,7 @@ import net.mamoe.mirai.contact.Contact
 import net.mamoe.mirai.contact.QQ
 import net.mamoe.mirai.network.protocol.tim.packet.action.FriendImageIdRequestPacket
 import net.mamoe.mirai.utils.ExternalImage
+import kotlin.jvm.Volatile
 
 // region Message Base
 /**
@@ -210,20 +211,24 @@ inline class Face(val id: FaceID) : Message {
  * 构造无初始元素的可修改的 [MessageChain]. 初始大小将会被设定为 8
  */
 @Suppress("FunctionName")
-fun MessageChain(): MessageChain = MessageChainImpl(ArrayList(8))
+fun MessageChain(): MessageChain = EmptyMessageChain()
 
 /**
  * 构造无初始元素的可修改的 [MessageChain]. 初始大小将会被设定为 [initialCapacity]
  */
 @Suppress("FunctionName")
-fun MessageChain(initialCapacity: Int): MessageChain = MessageChainImpl(ArrayList(initialCapacity))
+fun MessageChain(initialCapacity: Int): MessageChain =
+    if (initialCapacity == 0) EmptyMessageChain()
+    else MessageChainImpl(ArrayList(initialCapacity))
 
 /**
  * 构造 [MessageChain]
  * 若仅提供一个参数, 请考虑使用 [Message.toChain] 以优化性能
  */
 @Suppress("FunctionName")
-fun MessageChain(vararg messages: Message): MessageChain = MessageChainImpl(messages.toMutableList())
+fun MessageChain(vararg messages: Message): MessageChain =
+    if (messages.isEmpty()) EmptyMessageChain()
+    else MessageChainImpl(messages.toMutableList())
 
 /**
  * 构造 [MessageChain]
@@ -351,15 +356,17 @@ interface MessageChain : Message, MutableList<Message> {
 /**
  * 空的 [Message].
  *
- * 它不包含任何元素, 但维护一个 'lazy' 的 [delegate].
+ * 它不包含任何元素, 但维护一个 'lazy' 的 [MessageChainImpl].
  *
- * 只有在必要的时候才会创建 list, 如迭代([iterator]), 插入([add]), 连接([concat], [plus], [plusAssign])时.
+ * 只有在必要的时候(如迭代([iterator]), 插入([add]), 连接([concat], [plus], [plusAssign]))才会创建这个对象代表的 list
  *
- * 它是一个正常的 [Message] 和 [Message]. 可以做所有 [Message] 能做的事.
+ * 它是一个正常的 [Message] 和 [MessageChain]. 可以做所有 [Message] 能做的事.
  */
 class EmptyMessageChain : MessageChain {
-    private val delegate: MessageChainImpl by lazy { MessageChainImpl() }
-    private inline val initialized: Boolean get() = (::delegate as Lazy<*>).isInitialized()
+    private val delegate: MessageChain by lazy { MessageChainImpl().also { initialized = true } }
+
+    @Volatile
+    private var initialized: Boolean = false
 
     override fun subList(fromIndex: Int, toIndex: Int): MutableList<Message> =
         if (initialized) delegate.subList(
@@ -498,8 +505,9 @@ internal inline class MessageChainImpl constructor(
      */
     private val delegate: MutableList<Message>
 ) : Message, MutableList<Message>, MessageChain {
-    constructor() : this(ArrayList(8))
+    //constructor() : this(ArrayList(8))
     constructor(initialCapacity: Int) : this(ArrayList(initialCapacity))
+
     constructor(vararg messages: Message) : this(messages.toMutableList())
     constructor(messages: Iterable<Message>) : this(messages.toMutableList())
 

+ 6 - 5
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/internal/MessageDataInternal.kt

@@ -168,6 +168,7 @@ fun MessageChain.toPacket(forGroup: Boolean): ByteReadPacket = buildPacket {
 
                 is Image -> buildPacket {
                     when (id.value.length) {
+                        //   "{F61593B5-5B98-1798-3F47-2A91D32ED2FC}.jpg"
                         42 -> {
                             writeUByte(MessageType.GROUP_IMAGE.value)
 
@@ -188,6 +189,7 @@ fun MessageChain.toPacket(forGroup: Boolean): ByteReadPacket = buildPacket {
                             }
                         }
 
+                        //   "/01ee6426-5ff1-4cf0-8278-e8634d2909ef"
                         37 -> {
                             writeUByte(MessageType.FRIEND_IMAGE.value)
 
@@ -225,8 +227,7 @@ fun MessageChain.toPacket(forGroup: Boolean): ByteReadPacket = buildPacket {
                                 writeByte(0x02)
                                 //"46 52 25 46 60 30 59 4F 4A 5A 51 48 31 46 4A 53 4C 51 4C 4A 33 46 31 2E 6A 70 67".hexToBytes().stringOfWitch()
                                 //   writeShortLVString(filename)//图片文件名 FR%F`0YOJZQH1FJSLQLJ3F1.jpg
-                                require(id.value.length == 37) { "Illegal ImageId: ${id.value}" }
-                                writeShortLVString(id.value.substring(1..24) + ".gif")//图片文件名. 后缀不影响. 但无后缀会导致 PC QQ 无法显示这个图片
+                                writeShortLVString(id.value.substring(1..24) + ".gif")// 图片文件名. 后缀不影响. 但无后缀会导致 PC QQ 无法显示这个图片
                                 writeHex("03 00 04 00 00 02 A2 04")
                                 writeShortLVString(id.value)
                                 writeHex("14 00 04 03 00 00 00 0B 00 00 18")
@@ -234,10 +235,10 @@ fun MessageChain.toPacket(forGroup: Boolean): ByteReadPacket = buildPacket {
 
                                 writeHex("19 00 04 00 00 00 4E 1A 00 04 00 00 00 23 FF 00 63 16 20 20 39 39 31 30 20 38 38 31 43 42 20 20 20 20 20 20 20 ")
 
-                                writeStringUtf8("674e")//没有 "e" 服务器就不回复
+                                writeStringUtf8("674e")// 没有 "e" 服务器就不回复
 
                                 writeStringUtf8(id.value.substring(1..id.value.lastIndex - 4))//这一串文件名决定手机 QQ 保存的图片. 可以随意
-                                writeStringUtf8(".gif")//后缀要有
+                                writeStringUtf8(".gif")// 后缀似乎必须要有
                                 writeUByte(0x66u)
 
                                 //有时候 PC QQ 看不到发这些消息, 但手机可以. 可能是 ID 过期了, 手机有缓存而电脑没有
@@ -256,7 +257,7 @@ fun MessageChain.toPacket(forGroup: Boolean): ByteReadPacket = buildPacket {
                                 writeUByte(0x41u)
                             }
                         }
-                        else -> error("Illegal ImageId ${id.value}")
+                        else -> error("Illegal ImageId: ${id.value}")
                     }
                 }
 

+ 44 - 27
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/TIMBotNetworkHandler.kt

@@ -8,13 +8,11 @@ import kotlinx.coroutines.sync.withLock
 import kotlinx.io.core.*
 import kotlinx.io.pool.useInstance
 import net.mamoe.mirai.Bot
-import net.mamoe.mirai.event.ListeningStatus
-import net.mamoe.mirai.event.broadcast
+import net.mamoe.mirai.event.*
 import net.mamoe.mirai.event.events.BeforePacketSendEvent
 import net.mamoe.mirai.event.events.BotLoginSucceedEvent
 import net.mamoe.mirai.event.events.PacketSentEvent
 import net.mamoe.mirai.event.events.ServerPacketReceivedEvent
-import net.mamoe.mirai.event.subscribe
 import net.mamoe.mirai.network.BotNetworkHandler
 import net.mamoe.mirai.network.BotSession
 import net.mamoe.mirai.network.protocol.tim.handler.*
@@ -33,7 +31,7 @@ import kotlin.properties.Delegates
  *
  * JVM: 独立的 4 thread 调度器
  */
-expect val NetworkDispatcher: CoroutineDispatcher
+internal expect val NetworkDispatcher: CoroutineDispatcher
 
 /**
  * [BotNetworkHandler] 的 TIM PC 协议实现
@@ -75,7 +73,6 @@ internal class TIMBotNetworkHandler internal constructor(override val bot: Bot)
 
                 socket.resendTouch().takeIf { it != LoginResult.TIMEOUT }?.let { return@withContext it }
 
-                println()
                 bot.logger.warning("Timeout. Retrying next server")
 
                 socket.close()
@@ -91,7 +88,6 @@ internal class TIMBotNetworkHandler internal constructor(override val bot: Bot)
         require(size == 0) { "Already logged in" }
         val session = BotSession(bot, sessionKey, socket)
 
-        add(EventPacketHandler(session).asNode(EventPacketHandler))
         add(ActionPacketHandler(session).asNode(ActionPacketHandler))
         bot.logger.info("Successfully logged in")
     }
@@ -144,6 +140,8 @@ internal class TIMBotNetworkHandler internal constructor(override val bot: Bot)
                 } catch (e: ReadPacketInternalException) {
                     bot.logger.error("Socket channel read failed: ${e.message}")
                     continue
+                } catch (e: CancellationException) {
+                    return
                 } catch (e: Throwable) {
                     bot.logger.error("Caught unexpected exceptions", e)
                     continue
@@ -154,6 +152,7 @@ internal class TIMBotNetworkHandler internal constructor(override val bot: Bot)
                         continue
                     }// sometimes exceptions are thrown without this `if` clause
                 }
+
                 //buffer.resetForRead()
                 launch {
                     // `.use`: Ensure that the packet is consumed **totally**
@@ -164,9 +163,27 @@ internal class TIMBotNetworkHandler internal constructor(override val bot: Bot)
                         buffer.resetForWrite()
                         buffer.writeFully(it, 0, length)
                     }
-                    ByteReadPacket(buffer, IoBuffer.Pool).use {
+                    ByteReadPacket(buffer, IoBuffer.Pool).use { input ->
                         try {
-                            processPacket(it)
+                            input.discardExact(3)
+
+                            val id = matchPacketId(input.readUShort())
+                            val sequenceId = input.readUShort()
+
+                            input.discardExact(7)//4 for qq number, 3 for 0x00 0x00 0x00
+
+                            val packet = try {
+                                with(id.factory) {
+                                    loginHandler.provideDecrypter(id.factory)
+                                        .decrypt(input)
+                                        .decode(id, sequenceId, this@TIMBotNetworkHandler)
+                                }
+                            } finally {
+                                input.close()
+                            }
+
+
+                            handlePacket0(sequenceId, packet, id.factory)
                         } catch (e: Exception) {
                             bot.logger.error(e)
                         }
@@ -205,25 +222,19 @@ internal class TIMBotNetworkHandler internal constructor(override val bot: Bot)
             return receiving
         }
 
-        private suspend inline fun processPacket(input: ByteReadPacket) = with(input) {
-            discardExact(3)
-
-            val id = PacketId(readUShort())
-            val sequenceId = readUShort()
-
-            discardExact(7)//4 for qq number, 3 for 0x00 0x00 0x00. 但更可能是应该 discard 8
-
-            val packet: Packet = with(id.factory) {
-                try {
-                    loginHandler.provideDecrypter(id.factory)
-                        .decrypt(input)
-                        .decode(id, sequenceId, this@TIMBotNetworkHandler)
-                } finally {
-                    input.close()
-                }
+        private suspend fun <TPacket : Packet> handlePacket0(
+            sequenceId: UShort,
+            packet: TPacket,
+            factory: PacketFactory<TPacket, *>
+        ) {
+            if (!packet::class.annotations.filterIsInstance<NoLog>().any()) {
+                bot.logger.verbose("Packet received: $packet")
             }
 
-            bot.logger.verbose("Packet received: $packet")
+            when (packet) {
+                is Cancellable -> if ((packet as Cancellable).broadcast(coroutineContext).cancelled) return
+                is Subscribable -> packet.broadcast(coroutineContext)
+            }
 
             // Remove first to release the lock
             handlersLock.withLock {
@@ -236,6 +247,12 @@ internal class TIMBotNetworkHandler internal constructor(override val bot: Bot)
             if (ServerPacketReceivedEvent(bot, packet).broadcast().cancelled)
                 return
 
+            if (factory is SessionPacketFactory<*>) {
+                with(factory as SessionPacketFactory<TPacket>) {
+                    processPacket(packet)
+                }
+            }
+
             // They should be called in sequence because packet is lock-free
             loginHandler.onPacketReceived(packet)
             [email protected] {
@@ -270,7 +287,7 @@ internal class TIMBotNetworkHandler internal constructor(override val bot: Bot)
                     it::class.annotations.filterIsInstance<NoLog>().any()
                 }
             }?.let {
-                bot.logger.verbose("Packet sent:     $it")
+                bot.logger.verbose("Packet sent:     ${it::class.simpleName ?: "[OutgoingPacket]"}")
             }
 
             PacketSentEvent(bot, packet).broadcast()
@@ -455,7 +472,7 @@ internal class TIMBotNetworkHandler internal constructor(override val bot: Bot)
 
                 //是ClientPasswordSubmissionPacket之后服务器回复的可能之一
                 is SubmitPasswordPacket.LoginResponse.KeyExchange -> {
-                    this.privateKey = packet.privateKeyUpdate!!
+                    this.privateKey = packet.privateKeyUpdate
 
                     socket.sendPacket(
                         SubmitPasswordPacket(

+ 0 - 69
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/handler/EventPacketHandler.kt

@@ -1,69 +0,0 @@
-package net.mamoe.mirai.network.protocol.tim.handler
-
-import net.mamoe.mirai.contact.Group
-import net.mamoe.mirai.contact.GroupId
-import net.mamoe.mirai.contact.QQ
-import net.mamoe.mirai.contact.groupId
-import net.mamoe.mirai.event.broadcast
-import net.mamoe.mirai.event.events.*
-import net.mamoe.mirai.getGroup
-import net.mamoe.mirai.getQQ
-import net.mamoe.mirai.message.MessageChain
-import net.mamoe.mirai.network.BotSession
-import net.mamoe.mirai.network.protocol.tim.packet.EventPacket
-import net.mamoe.mirai.network.protocol.tim.packet.FriendStatusChanged
-import net.mamoe.mirai.network.protocol.tim.packet.Packet
-import net.mamoe.mirai.network.protocol.tim.packet.action.FriendImageIdRequestPacket
-import net.mamoe.mirai.network.protocol.tim.packet.action.SendFriendMessagePacket
-import net.mamoe.mirai.network.protocol.tim.packet.action.SendGroupMessagePacket
-import net.mamoe.mirai.network.qqAccount
-
-/**
- * 处理消息事件, 承担消息发送任务.
- *
- * @author Him188moe
- */
-@Suppress("EXPERIMENTAL_API_USAGE")
-class EventPacketHandler(session: BotSession) : PacketHandler(session) {
-    companion object Key : PacketHandler.Key<EventPacketHandler>
-
-    override suspend fun onPacketReceived(packet: Packet): Unit = with(session) {
-        when (packet) {
-            is EventPacket.FriendMessage -> {
-                if (!packet.isPrevious) FriendMessageEvent(bot, bot.getQQ(packet.qq), packet.message) else null
-            }
-
-            is EventPacket.GroupMessage -> {
-                if (packet.qq == bot.account.id) return
-
-                GroupMessageEvent(
-                    bot, bot.getGroup(GroupId(packet.groupNumber)), bot.getQQ(packet.qq), packet.message, packet.senderPermission, packet.senderName
-                )
-            }
-
-            is EventPacket.FriendConversationInitialize -> FriendConversationInitializedEvent(bot, bot.getQQ(packet.qq))
-            is FriendStatusChanged -> FriendOnlineStatusChangedEvent(bot, bot.getQQ(packet.qq), packet.status)
-            is FriendImageIdRequestPacket.Response -> packet.imageId?.let { FriendImageIdObtainedEvent(bot, it) }
-
-            is EventPacket.MemberPermissionChange ->
-                MemberPermissionChangedEvent(bot, bot.getGroup(packet.groupId.groupId()), bot.getQQ(packet.qq), packet.kind)
-
-            else -> null
-        }?.broadcast()
-    }
-
-    suspend fun sendFriendMessage(qq: QQ, message: MessageChain) {
-        session.socket.sendPacket(SendFriendMessagePacket(session.qqAccount, qq.id, session.sessionKey, message))
-    }
-
-    suspend fun sendGroupMessage(group: Group, message: MessageChain) {
-        session.socket.sendPacket(
-            SendGroupMessagePacket(
-                session.qqAccount,
-                group.internalId,
-                session.sessionKey,
-                message
-            )
-        )
-    }
-}

+ 3 - 3
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/Annotations.kt

@@ -2,7 +2,7 @@
 
 package net.mamoe.mirai.network.protocol.tim.packet
 
-import net.mamoe.mirai.event.Event
+import net.mamoe.mirai.event.Subscribable
 import kotlin.reflect.KClass
 
 
@@ -27,11 +27,11 @@ inline val AnnotatedId.value: UShort get() = id.value
 @Retention(AnnotationRetention.BINARY)
 @Target(AnnotationTarget.CLASS)
 annotation class CorrespondingEvent(
-    val eventClass: KClass<out Event>
+    val eventClass: KClass<out Subscribable>
 )
 
 /**
- * 版本信息
+ * 包的最后一次修改时间, 和分析时使用的 TIM 版本
  */
 @MustBeDocumented
 @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)

+ 1 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/Decrypters.kt

@@ -2,6 +2,7 @@ package net.mamoe.mirai.network.protocol.tim.packet
 
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.IoBuffer
+import net.mamoe.mirai.utils.decryptBy
 
 
 /**

+ 8 - 9
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/OutgoingPacket.kt

@@ -6,6 +6,7 @@ import kotlinx.io.core.BytePacketBuilder
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.use
 import kotlinx.io.core.writeUShort
+import net.mamoe.mirai.network.BotNetworkHandler
 import net.mamoe.mirai.network.protocol.tim.TIMProtocol
 import net.mamoe.mirai.utils.io.encryptAndWrite
 import net.mamoe.mirai.utils.io.writeHex
@@ -24,21 +25,19 @@ class OutgoingPacket(
     private val name: String by lazy {
         name ?: packetId.toString()
     }
-
-    constructor(id: PacketId, sequenceId: UShort, delegate: ByteReadPacket) : this(null, id, sequenceId, delegate)
-
-    constructor(annotation: AnnotatedId, sequenceId: UShort, delegate: ByteReadPacket) :
-            this(annotation.toString(), annotation.id, sequenceId, delegate)
-
-    override fun toString(): String = packetToString(packetId.value, sequenceId, name)
 }
 
 /**
  * 登录完成建立 session 之后发出的包.
  * 均使用 sessionKey 加密
+ *
+ * @param TPacket invariant
  */
-abstract class SessionPacketFactory<out TPacket : Packet> : PacketFactory<TPacket, SessionKey>(SessionKey) {
-    final override fun decrypt(input: ByteReadPacket, decrypter: SessionKey): ByteReadPacket = decrypter.decrypt(input)
+abstract class SessionPacketFactory<TPacket : Packet> : PacketFactory<TPacket, SessionKey>(SessionKey) {
+    /**
+     * 在 [BotNetworkHandler] 下处理这个包. 广播事件等.
+     */
+    open suspend fun BotNetworkHandler<*>.processPacket(packet: TPacket) {}
 }
 
 /**

+ 4 - 305
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/Packet.kt

@@ -1,321 +1,20 @@
-@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
-
 package net.mamoe.mirai.network.protocol.tim.packet
 
-import kotlinx.atomicfu.atomic
 import kotlinx.io.core.ByteReadPacket
-import kotlinx.io.core.IoBuffer
-import net.mamoe.mirai.event.events.FriendConversationInitializedEvent
-import net.mamoe.mirai.event.events.FriendMessageEvent
-import net.mamoe.mirai.event.events.GroupMessageEvent
-import net.mamoe.mirai.event.events.MemberPermissionChangedEvent
-import net.mamoe.mirai.message.MessageChain
-import net.mamoe.mirai.message.NullMessageChain
-import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.protocol.tim.packet.action.*
-import net.mamoe.mirai.network.protocol.tim.packet.event.EventPacketFactory
-import net.mamoe.mirai.network.protocol.tim.packet.event.SenderPermission
-import net.mamoe.mirai.network.protocol.tim.packet.login.*
-import net.mamoe.mirai.utils.io.toUHexString
-import kotlin.properties.ReadWriteProperty
-import kotlin.reflect.KProperty
-import kotlin.reflect.KProperty0
-import kotlin.reflect.KProperty1
-
-/**
- * 一种数据包的处理工厂. 它可以解密解码服务器发来的这个包, 也可以编码加密要发送给服务器的这个包
- * 应由一个 `object` 实现, 且实现 `operator fun invoke`
- *
- * @param TPacket 服务器回复包解析结果
- * @param TDecrypter 服务器回复包解密器
- */
-abstract class PacketFactory<out TPacket : Packet, TDecrypter : Decrypter>(internal val decrypterType: DecrypterType<TDecrypter>) {
-
-    /**
-     * 2 Ubyte.
-     * 读取注解 [AnnotatedId]
-     */
-    private val annotatedId: AnnotatedId
-        get() = (this::class.annotations.firstOrNull { it is AnnotatedId } as? AnnotatedId)
-            ?: error("Annotation AnnotatedId not found")
-
-    /**
-     * 包 ID.
-     */
-    open val id: PacketId by lazy { annotatedId.id }
-
-    init {
-        @Suppress("LeakingThis")
-        PacketFactoryList.add(this)
-    }
-
-    /**
-     * **解码**服务器的回复数据包
-     */
-    abstract suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): TPacket
-
-    /**
-     * **解密**服务器的回复数据包. 这个函数将会被 [BotNetworkHandler]
-     */
-    open fun decrypt(input: ByteReadPacket, decrypter: TDecrypter): ByteReadPacket = decrypter.decrypt(input)
-
-    companion object {
-        private val sequenceIdInternal = atomic(1)
-
-        @PublishedApi
-        internal fun atomicNextSequenceId(): UShort = sequenceIdInternal.getAndIncrement().toUShort()
-    }
-}
-
-object PacketFactoryList : MutableList<PacketFactory<*, *>> by mutableListOf()
-
-
-object UnknownPacketFactory : SessionPacketFactory<UnknownPacket>() {
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): UnknownPacket {
-
-        return UnknownPacket
-    }
-}
-
-object IgnoredPacketFactory : SessionPacketFactory<IgnoredPacket>() {
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): IgnoredPacket {
-        return IgnoredPacket
-    }
-}
-
-// region Packet id
-
-/**
- * 通过 [value] 匹配一个 [IgnoredPacketId] 或 [KnownPacketId], 无匹配则返回一个 [UnknownPacketId].
- */
-@Suppress("FunctionName")
-fun PacketId(value: UShort): PacketId =
-    IgnoredPacketIds.firstOrNull { it.value == value } ?: KnownPacketId.values().firstOrNull { it.value == value } ?: UnknownPacketId(value)
-
-/**
- * 包 ID.
- */
-interface PacketId {
-    val value: UShort
-    val factory: PacketFactory<*, *>
-}
-
-/**
- * 用于代表 `null`. 调用任何属性时都将会得到一个 [error]
- */
-object NullPacketId : PacketId {
-    override val factory: PacketFactory<*, *> get() = error("uninitialized")
-    override val value: UShort get() = error("uninitialized")
-}
-
-/**
- * 未知的 [PacketId]
- */
-inline class UnknownPacketId(override inline val value: UShort) : PacketId {
-    override val factory: PacketFactory<*, *> get() = UnknownPacketFactory
-}
-
-object IgnoredPacketIds : List<IgnoredPacketId> by {
-    listOf<UShort>(
-    ).map { IgnoredPacketId(it.toUShort()) }
-}()
-
-inline class IgnoredPacketId constructor(override val value: UShort) : PacketId {
-    override val factory: PacketFactory<*, *> get() = IgnoredPacketFactory
-}
-
-/**
- * 已知的 [PacketId]. 所有在 Mirai 中实现过的包都会使用这些 Id
- */
-@Suppress("unused")
-enum class KnownPacketId(override inline val value: UShort, override inline val factory: PacketFactory<*, *>) :
-    PacketId {
-    inline TOUCH(0x0825u, TouchPacket),
-    inline SESSION_KEY(0x0828u, RequestSessionPacket),
-    inline LOGIN(0x0836u, SubmitPasswordPacket),
-    inline CAPTCHA(0x00BAu, CaptchaPacket),
-    inline SERVER_EVENT_1(0x00CEu, EventPacketFactory),
-    inline SERVER_EVENT_2(0x0017u, EventPacketFactory),
-    inline FRIEND_ONLINE_STATUS_CHANGE(0x0081u, FriendOnlineStatusChangedPacket),
-    inline CHANGE_ONLINE_STATUS(0x00ECu, ChangeOnlineStatusPacket),
-
-    inline HEARTBEAT(0x0058u, HeartbeatPacket),
-    inline S_KEY(0x001Du, RequestSKeyPacket),
-    inline ACCOUNT_INFO(0x005Cu, RequestAccountInfoPacket),
-    inline SEND_GROUP_MESSAGE(0x0002u, SendGroupMessagePacket),
-    inline SEND_FRIEND_MESSAGE(0x00CDu, SendFriendMessagePacket),
-    inline CAN_ADD_FRIEND(0x00A7u, CanAddFriendPacket),
-    inline GROUP_IMAGE_ID(0x0388u, GroupImageIdRequestPacket),
-    inline FRIEND_IMAGE_ID(0x0352u, FriendImageIdRequestPacket),
-
-    inline REQUEST_PROFILE_AVATAR(0x0031u, RequestProfilePicturePacket),
-    inline REQUEST_PROFILE_DETAILS(0x003Cu, RequestProfilePicturePacket),
-    // @Suppress("DEPRECATION")
-    // inline SUBMIT_IMAGE_FILE_NAME(0x01BDu, SubmitImageFilenamePacket),
-
-    ;
-
-    override fun toString(): String = factory.let { it::class.simpleName } ?: this.name
-}
-
-// endregion
-
-object IgnoredPacket : Packet
-
-sealed class EventPacket {
-    class GroupFileUpload(inline val xmlMessage: String) : Packet
-
-    @PacketVersion(date = "2019.11.2", timVersion = "2.3.2.21173")
-    class AndroidDeviceStatusChange(inline val kind: Kind) : Packet {
-        enum class Kind {
-            ONLINE,
-            OFFLINE
-        }
-    }
-
-    @CorrespondingEvent(MemberPermissionChangedEvent::class)
-    class MemberPermissionChange : Packet {
-        var groupId: UInt = 0u
-        var qq: UInt = 0u
-        lateinit var kind: MemberPermissionChangedEvent.Kind
-    }
-
-    @CorrespondingEvent(FriendConversationInitializedEvent::class)
-    @PacketVersion(date = "2019.11.2", timVersion = "2.3.2.21173")
-    class FriendConversationInitialize : Packet {
-        var qq: UInt = 0u
-    }
-
-    @CorrespondingEvent(FriendMessageEvent::class)
-    data class FriendMessage(
-        val qq: UInt,
-        /**
-         * 是否是在这次登录之前的消息, 即消息记录
-         */
-        val isPrevious: Boolean,
-        val message: MessageChain
-    ) : Packet
-
-    @CorrespondingEvent(GroupMessageEvent::class)
-    class GroupMessage : Packet { // TODO: 2019/11/6 改为 data class
-        var groupNumber: UInt = 0u
-            internal set
-        var qq: UInt = 0u
-            internal set
-        lateinit var senderName: String
-            internal set
-        /**
-         * 发送方权限.
-         */
-        lateinit var senderPermission: SenderPermission
-            internal set
-        var message: MessageChain = NullMessageChain
-            internal set
-    }
-}
 
 /**
  * 一个包的数据 (body)
  */
-@Suppress("unused")
 interface Packet
-//// TODO: 2019/11/5 Packet.toString
+
+object IgnoredPacket : Packet
 
 /**
  * 未知的包.
  */
-object UnknownPacket : Packet {
-    // TODO: 2019/11/5 添加包数据用于调试
-}
+class UnknownPacket(val id: PacketId, val body: ByteReadPacket) : Packet
 
 /**
  * 仅用于替换类型应为 [Unit] 的情况
  */
-object NoPacket : Packet
-
-
-// region Internal utils
-
-private object PacketNameFormatter {
-    private var longestNameLength: Int = 43
-    fun adjustName(name: String): String =
-        if (name.length > longestNameLength) {
-            longestNameLength = name.length
-            name
-        } else " ".repeat(longestNameLength - name.length) + name
-}
-
-private object IgnoreIdListEquals : List<String> by listOf(
-    "idHex",
-    "id",
-    "eventIdentity",
-    "packetId",
-    "sequenceIdInternal",
-    "sequenceId",
-    "fixedId",
-    "idByteArray",
-    "encoded",
-    "packet",
-    "EMPTY_ID_HEX",
-    "input",
-    "sequenceId",
-    "output",
-    "bot",
-    "UninitializedByteReadPacket",
-    "sessionKey"
-)
-
-private object IgnoreIdListInclude : List<String> by listOf(
-    "Companion",
-    "EMPTY_ID_HEX",
-    "input",
-    "output",
-    "this\$",
-    "\$\$delegatedProperties",
-    "UninitializedByteReadPacket",
-    "\$FU",
-    "RefVolatile"
-)
-
-
-/**
- * 这个方法会翻倍内存占用, 考虑修改.
- */
-@Suppress("UNCHECKED_CAST")
-internal fun Packet.packetToString(id: UShort, sequenceId: UShort, name: String = this::class.simpleName.toString()): String =
-    id.toString()
-/*PacketNameFormatter.adjustName(name + "(${(id.toInt().shl(16) or sequenceId.toInt()).toUHexString()})") +
-        this::class.members
-            .filterIsInstance<KProperty<*>>()
-            .filterNot { it.isConst || it.isSuspend || it.visibility != KVisibility.PUBLIC }
-            .filterNot { prop -> prop.name in IgnoreIdListEquals || IgnoreIdListInclude.any { it in prop.name } }
-            .joinToString(", ", "{", "}") { it.briefDescription(this@packetToString) }*/
-
-@Suppress("UNCHECKED_CAST")
-private fun KProperty<*>.briefDescription(thisRef: Packet): String =
-    try {
-        when (this) {
-            is KProperty0<*> -> get()
-            is KProperty1<*, *> -> (this as KProperty1<in Packet, Any>).get(thisRef)
-            else -> null
-        }
-    } catch (e: Exception) {
-        null
-    }.let { value: Any? ->
-        @Suppress("UNCHECKED_CAST")
-        name.replace("\$delegate", "") + "=" + when (value) {
-            null -> "_"
-            is ByteArray -> value.toUHexString()
-            is UByteArray -> value.toUHexString()
-            is ByteReadPacket -> "[ByteReadPacket(${value.remaining})]"
-            is IoBuffer -> "[IoBuffer(${value.readRemaining})]"
-            is Lazy<*> -> "[Lazy]"
-            is ReadWriteProperty<*, *> -> (value as? ReadWriteProperty<Packet, *>)?.getValue(
-                thisRef,
-                this
-            ) ?: "[UnknownProperty]"
-            else -> value.toString().replace("\n", """\n""")
-        }
-    }
-
-// endregion
+object NoPacket : Packet

+ 73 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/PacketFactory.kt

@@ -0,0 +1,73 @@
+@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
+
+package net.mamoe.mirai.network.protocol.tim.packet
+
+import kotlinx.atomicfu.atomic
+import kotlinx.io.core.ByteReadPacket
+import kotlinx.io.pool.useInstance
+import net.mamoe.mirai.network.BotNetworkHandler
+import net.mamoe.mirai.utils.io.ByteArrayPool
+import net.mamoe.mirai.utils.io.toUHexString
+
+object PacketFactoryList : MutableList<PacketFactory<*, *>> by mutableListOf()
+
+/**
+ * 一种数据包的处理工厂. 它可以解密解码服务器发来的这个包, 也可以编码加密要发送给服务器的这个包
+ * 应由一个 `object` 实现, 且实现 `operator fun invoke`
+ *
+ * @param TPacket 服务器回复包解析结果
+ * @param TDecrypter 服务器回复包解密器
+ */
+abstract class PacketFactory<out TPacket : Packet, TDecrypter : Decrypter>(internal val decrypterType: DecrypterType<TDecrypter>) {
+
+    /**
+     * 2 Ubyte.
+     * 读取注解 [AnnotatedId]
+     */
+    private val annotatedId: AnnotatedId
+        get() = (this::class.annotations.firstOrNull { it is AnnotatedId } as? AnnotatedId)
+            ?: error("Annotation AnnotatedId not found")
+
+    /**
+     * 包 ID.
+     */
+    open val id: PacketId by lazy { annotatedId.id }
+
+    init {
+        @Suppress("LeakingThis")
+        PacketFactoryList.add(this)
+    }
+
+    /**
+     * **解码**服务器的回复数据包
+     */
+    abstract suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): TPacket
+
+    companion object {
+        private val sequenceIdInternal = atomic(1)
+
+        @PublishedApi
+        internal fun atomicNextSequenceId(): UShort = sequenceIdInternal.getAndIncrement().toUShort()
+    }
+}
+
+object UnknownPacketFactory : SessionPacketFactory<UnknownPacket>() {
+    override suspend fun BotNetworkHandler<*>.processPacket(packet: UnknownPacket) {
+        ByteArrayPool.useInstance {
+            packet.body.readAvailable(it)
+            bot.logger.debug("Unknown packet(${packet.id.value}) body = " + it.toUHexString())
+        }
+        packet.body.close()
+    }
+
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): UnknownPacket {
+        return UnknownPacket(id, this)
+    }
+}
+
+object IgnoredPacketFactory : SessionPacketFactory<IgnoredPacket>() {
+    override suspend fun BotNetworkHandler<*>.processPacket(packet: IgnoredPacket) {
+    }
+
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): IgnoredPacket = IgnoredPacket
+}

+ 82 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/PacketId.kt

@@ -0,0 +1,82 @@
+@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
+
+package net.mamoe.mirai.network.protocol.tim.packet
+
+import net.mamoe.mirai.network.protocol.tim.packet.action.*
+import net.mamoe.mirai.network.protocol.tim.packet.event.EventPacketFactory
+import net.mamoe.mirai.network.protocol.tim.packet.event.FriendOnlineStatusChangedPacket
+import net.mamoe.mirai.network.protocol.tim.packet.login.*
+
+
+/**
+ * 通过 [value] 匹配一个 [IgnoredPacketId] 或 [KnownPacketId], 无匹配则返回一个 [UnknownPacketId].
+ */
+fun matchPacketId(value: UShort): PacketId =
+    IgnoredPacketIds.firstOrNull { it.value == value } ?: KnownPacketId.values().firstOrNull { it.value == value } ?: UnknownPacketId(value)
+
+/**
+ * 包 ID.
+ */
+interface PacketId {
+    val value: UShort
+    val factory: PacketFactory<*, *>
+}
+
+/**
+ * 用于代表 `null`. 调用任何属性时都将会得到一个 [error]
+ */
+@Suppress("unused")
+object NullPacketId : PacketId {
+    override val factory: PacketFactory<*, *> get() = error("uninitialized")
+    override val value: UShort get() = error("uninitialized")
+}
+
+/**
+ * 未知的 [PacketId]
+ */
+inline class UnknownPacketId(override inline val value: UShort) : PacketId {
+    override val factory: PacketFactory<*, *> get() = UnknownPacketFactory
+}
+
+object IgnoredPacketIds : List<IgnoredPacketId> by {
+    listOf<UShort>(
+    ).map { IgnoredPacketId(it.toUShort()) }
+}()
+
+inline class IgnoredPacketId constructor(override val value: UShort) : PacketId {
+    override val factory: PacketFactory<*, *> get() = IgnoredPacketFactory
+}
+
+/**
+ * 已知的 [matchPacketId]. 所有在 Mirai 中实现过的包都会使用这些 Id
+ */
+@Suppress("unused")
+enum class KnownPacketId(override inline val value: UShort, override inline val factory: PacketFactory<*, *>) :
+    PacketId {
+    inline TOUCH(0x0825u, TouchPacket),
+    inline SESSION_KEY(0x0828u, RequestSessionPacket),
+    inline LOGIN(0x0836u, SubmitPasswordPacket),
+    inline CAPTCHA(0x00BAu, CaptchaPacket),
+    inline SERVER_EVENT_1(0x00CEu, EventPacketFactory),
+    inline SERVER_EVENT_2(0x0017u, EventPacketFactory),
+    inline FRIEND_ONLINE_STATUS_CHANGE(0x0081u, FriendOnlineStatusChangedPacket),
+    inline CHANGE_ONLINE_STATUS(0x00ECu, ChangeOnlineStatusPacket),
+
+    inline HEARTBEAT(0x0058u, HeartbeatPacket),
+    inline S_KEY(0x001Du, RequestSKeyPacket),
+    inline ACCOUNT_INFO(0x005Cu, RequestAccountInfoPacket),
+    inline SEND_GROUP_MESSAGE(0x0002u, SendGroupMessagePacket),
+    inline SEND_FRIEND_MESSAGE(0x00CDu, SendFriendMessagePacket),
+    inline CAN_ADD_FRIEND(0x00A7u, CanAddFriendPacket),
+    inline GROUP_IMAGE_ID(0x0388u, GroupImageIdRequestPacket),
+    inline FRIEND_IMAGE_ID(0x0352u, FriendImageIdRequestPacket),
+
+    inline REQUEST_PROFILE_AVATAR(0x0031u, RequestProfilePicturePacket),
+    inline REQUEST_PROFILE_DETAILS(0x003Cu, RequestProfilePicturePacket),
+    // @Suppress("DEPRECATION")
+    // inline SUBMIT_IMAGE_FILE_NAME(0x01BDu, SubmitImageFilenamePacket),
+
+    ;
+
+    override fun toString(): String = factory.let { it::class.simpleName } ?: this.name
+}

+ 0 - 32
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/ServerPacket.kt

@@ -1,32 +0,0 @@
-@file:Suppress("EXPERIMENTAL_API_USAGE")
-
-package net.mamoe.mirai.network.protocol.tim.packet
-
-import kotlinx.io.core.ByteReadPacket
-import kotlinx.io.core.IoBuffer
-import kotlinx.io.pool.useInstance
-import net.mamoe.mirai.utils.decryptBy
-import net.mamoe.mirai.utils.io.ByteArrayPool
-import net.mamoe.mirai.utils.io.hexToBytes
-
-
-fun ByteReadPacket.decryptBy(key: ByteArray): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) }
-fun ByteReadPacket.decryptBy(key: IoBuffer): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) }
-fun ByteReadPacket.decryptBy(keyHex: String): ByteReadPacket = decryptBy(keyHex.hexToBytes())
-
-inline fun <R> ByteReadPacket.decryptAsByteArray(key: ByteArray, consumer: (ByteArray) -> R): R =
-    ByteArrayPool.useInstance {
-        val length = remaining.toInt()
-        readFully(it, 0, length)
-        consumer(it.decryptBy(key, length))
-    }.also { close() }
-
-inline fun <R> ByteReadPacket.decryptAsByteArray(keyHex: String, consumer: (ByteArray) -> R): R =
-    this.decryptAsByteArray(keyHex.hexToBytes(), consumer)
-
-inline fun <R> ByteReadPacket.decryptAsByteArray(key: IoBuffer, consumer: (ByteArray) -> R): R =
-    ByteArrayPool.useInstance {
-        val length = remaining.toInt()
-        readFully(it, 0, length)
-        consumer(it.decryptBy(key, length))
-    }.also { close() }

+ 0 - 42
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/UnknownServerPacket.kt

@@ -1,42 +0,0 @@
-@file:Suppress("EXPERIMENTAL_API_USAGE")
-
-package net.mamoe.mirai.network.protocol.tim.packet
-
-/*
-ID: 00 17
-
-长度 95
-76 E4 B8 DD //1994701021
-76 E4 B8 DD //1994701021
-00 0B B9 A9 09 90 BB 54 1F //类似Event的uniqueId?
-40 02
-10 00 00 00
-18 00
-08 00
-02 00
-01 00
-09 00
-06 41 4B DA 4C 00 00
-00 0A
-00 04
-01 00 00 00 00 00
-00 06 00 00
-00 0E
-08 02
-1A 02 08 49 0A 0C 08 A2 FF 8C F0
-03 10 CA EB 8B ED 05
-
-或者
-
-长度63
-00 00 27 10 76 E4 B8 DD
-00 09 ED 26 64 73 0E CA 1F 40
-00 12 00 00
-00 08
-00 0A
-00 04
-01 00 00
-00 02
-
-值都是一样的.
- */

+ 5 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/AddContact.kt

@@ -162,6 +162,11 @@ object AddFriendPacket : SessionPacketFactory<AddFriendPacket.AddFriendResponse>
         }
     }
 
+    override suspend fun BotNetworkHandler<*>.processPacket(packet: AddFriendResponse) {
+
+    }
+
+
     class AddFriendResponse : Packet
 
 

+ 0 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/GradeInfo.kt → mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/GradeInfo.kt


+ 92 - 76
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/UploadImage.kt

@@ -10,11 +10,10 @@ import io.ktor.http.userAgent
 import kotlinx.coroutines.withContext
 import kotlinx.io.core.*
 import net.mamoe.mirai.contact.*
-import net.mamoe.mirai.event.events.FriendImageIdObtainedEvent
 import net.mamoe.mirai.message.ImageId
 import net.mamoe.mirai.network.BotNetworkHandler
 import net.mamoe.mirai.network.protocol.tim.packet.*
-import net.mamoe.mirai.network.protocol.tim.packet.action.FriendImageIdRequestPacket.Response.State.*
+import net.mamoe.mirai.network.protocol.tim.packet.event.EventPacket
 import net.mamoe.mirai.network.qqAccount
 import net.mamoe.mirai.qqAccount
 import net.mamoe.mirai.utils.*
@@ -40,20 +39,20 @@ suspend fun Group.uploadImage(image: ExternalImage): ImageId = withSession {
     GroupImageIdRequestPacket(bot.qqAccount, internalId, image, sessionKey)
         .sendAndExpect<GroupImageIdRequestPacket.Response, Unit> {
             withContext(userContext) {
-                when (it.state) {
-                    GroupImageIdRequestPacket.Response.State.REQUIRE_UPLOAD -> httpClient.postImage(
+                when (it) {
+                    is GroupImageIdRequestPacket.Response.RequireUpload -> httpClient.postImage(
                         htcmd = "0x6ff0071",
                         uin = bot.qqAccount,
                         groupId = GroupId(id),
                         imageInput = image.input,
                         inputSize = image.inputSize,
-                        uKeyHex = it.uKey!!.toUHexString("")
+                        uKeyHex = it.uKey.toUHexString("")
                     )
 
-                    GroupImageIdRequestPacket.Response.State.ALREADY_EXISTS -> {
+                    is GroupImageIdRequestPacket.Response.AlreadyExists -> {
                     }
 
-                    GroupImageIdRequestPacket.Response.State.OVER_FILE_SIZE_MAX -> throw OverFileSizeMaxException()
+                    is GroupImageIdRequestPacket.Response.OverFileSizeMax -> throw OverFileSizeMaxException()
                 }
             }
         }.join()
@@ -71,23 +70,23 @@ suspend fun Group.uploadImage(image: ExternalImage): ImageId = withSession {
 suspend fun QQ.uploadImage(image: ExternalImage): ImageId = bot.withSession {
     FriendImageIdRequestPacket(qqAccount, sessionKey, id, image)
         .sendAndExpect<FriendImageIdRequestPacket.Response, ImageId> {
-            when (it.state) {
-                REQUIRE_UPLOAD -> httpClient.postImage(
-                    htcmd = "0x6ff0070",
-                    uin = bot.qqAccount,
-                    groupId = null,
-                    uKeyHex = it.uKey!!.toUHexString(""),
-                    imageInput = image.input,
-                    inputSize = image.inputSize
-                )
-
-                ALREADY_EXISTS -> {
+            return@sendAndExpect when (it) {
+                is FriendImageIdRequestPacket.Response.RequireUpload -> {
+                    httpClient.postImage(
+                        htcmd = "0x6ff0070",
+                        uin = bot.qqAccount,
+                        groupId = null,
+                        uKeyHex = it.uKey.toUHexString(""),
+                        imageInput = image.input,
+                        inputSize = image.inputSize
+                    )
+                    it.imageId
                 }
 
-                OVER_FILE_SIZE_MAX -> throw OverFileSizeMaxException()
-            }
+                is FriendImageIdRequestPacket.Response.AlreadyExists -> it.imageId
 
-            it.imageId!!
+                is FriendImageIdRequestPacket.Response.OverFileSizeMax -> throw OverFileSizeMaxException()
+            }
         }.await()
 }
 
@@ -187,14 +186,14 @@ object SubmitImageFilenamePacket : PacketFactory {
  * - 服务器未存有, 返回一个 key 用于客户端上传
  */
 @AnnotatedId(KnownPacketId.FRIEND_IMAGE_ID)
-@PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173")
+@PacketVersion(date = "2019.11.1", timVersion = "2.3.2.21173")
 object FriendImageIdRequestPacket : SessionPacketFactory<FriendImageIdRequestPacket.Response>() {
     operator fun invoke(
         bot: UInt,
         sessionKey: SessionKey,
         target: UInt,
         image: ExternalImage
-    ) = buildOutgoingPacket {
+    ): OutgoingPacket = buildOutgoingPacket {
         writeQQ(bot)
         writeHex("04 00 00 00 01 2E 01 00 00 69 35 00 00 00 00 00 00 00 00")
 
@@ -255,58 +254,73 @@ object FriendImageIdRequestPacket : SessionPacketFactory<FriendImageIdRequestPac
         }
     }
 
-    @CorrespondingEvent(FriendImageIdObtainedEvent::class)
-    @PacketVersion(date = "2019.11.1", timVersion = "2.3.2.21173")
-    class Response : Packet {
-        /**
-         * 访问 HTTP API 时需要使用的一个 key. 128 位
-         */
-        var uKey: ByteArray? = null
-
-        /**
-         * 发送消息时使用的 id
-         */
-        var imageId: ImageId? = null
 
-        lateinit var state: State
-
-        enum class State {
+    sealed class Response : EventPacket {
+        data class RequireUpload(
             /**
-             * 需要上传. 此时 [uKey], [imageId] 均不为 `null`
+             * 访问 HTTP API 时需要使用的一个 key. 128 位
              */
-            REQUIRE_UPLOAD,
+            val uKey: ByteArray,
             /**
-             * 服务器已有这个图片. 此时 [uKey] 为 `null`, [imageId] 不为 `null`
+             * 发送消息时使用的 id
              */
-            ALREADY_EXISTS,
+            val imageId: ImageId
+        ) : Response() {
+            override fun equals(other: Any?): Boolean {
+                if (this === other) return true
+                if (other !is RequireUpload) return false
+
+                if (!uKey.contentEquals(other.uKey)) return false
+                if (imageId != other.imageId) return false
+
+                return true
+            }
+
+            override fun hashCode(): Int {
+                var result = uKey.contentHashCode()
+                result = 31 * result + imageId.hashCode()
+                return result
+            }
+        }
+
+        data class AlreadyExists(
             /**
-             * 图片过大. 此时 [uKey], [imageId] 均为 `null`
+             * 发送消息时使用的 id
              */
-            OVER_FILE_SIZE_MAX,
+            val imageId: ImageId
+        ) : Response()
+
+        /**
+         * 超过文件大小上限
+         */
+        object OverFileSizeMax : Response() {
+            override fun toString(): String = this::class.simpleName!!
         }
     }
 
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): Response = Response().apply {
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): Response {
         discardExact(6)
+
         if (readUByte() != UByte.MIN_VALUE) {
             discardExact(60)
 
             @Suppress("ControlFlowWithEmptyBody")
             while (readUByte().toUInt() != 0x4Au);
 
-            uKey = readBytes(readUnsignedVarInt().toInt())//128
+            val uKey = readBytes(readUnsignedVarInt().toInt())//128
 
             discardExact(1)//52, id
-            imageId = ImageId(readString(readUnsignedVarInt().toInt()))//37
-            state = REQUIRE_UPLOAD
+            val imageId = ImageId(readString(readUnsignedVarInt().toInt()))//37
+            return Response.RequireUpload(uKey, imageId)
         } else {
             val toDiscard = readUByte().toInt() - 37
-            if (toDiscard < 0) {
-                state = OVER_FILE_SIZE_MAX
+
+            return if (toDiscard < 0) {
+                Response.OverFileSizeMax
             } else {
                 discardExact(toDiscard)
-                imageId = ImageId(readString(37))
-                state = ALREADY_EXISTS
+                val imageId = ImageId(readString(37))
+                Response.AlreadyExists(imageId)
             }
         }
     }
@@ -378,42 +392,44 @@ object GroupImageIdRequestPacket : SessionPacketFactory<GroupImageIdRequestPacke
 
     private val value0x6A: UByteArray = ubyteArrayOf(0x05u, 0x32u, 0x36u, 0x36u, 0x35u, 0x36u)
 
-    @PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173")
-    class Response : Packet {
-        lateinit var state: State
+    sealed class Response : EventPacket {
+        data class RequireUpload(
+            /**
+             * 访问 HTTP API 时需要使用的一个 key. 128 位
+             */
+            val uKey: ByteArray
+        ) : Response() {
+            override fun equals(other: Any?): Boolean {
+                if (this === other) return true
+                if (other !is RequireUpload) return false
+                if (!uKey.contentEquals(other.uKey)) return false
+                return true
+            }
+
+            override fun hashCode(): Int = uKey.contentHashCode()
+        }
+
+        object AlreadyExists : Response() {
+            override fun toString(): String = this::class.simpleName!!
+        }
 
         /**
-         * 访问 HTTP API 时需要使用的一个 key. 128 位
+         * 超过文件大小上限
          */
-        var uKey: ByteArray? = null
-
-        enum class State {
-            /**
-             * 需要上传. 此时 [uKey] 不为 `null`
-             */
-            REQUIRE_UPLOAD,
-            /**
-             * 服务器已有这个图片. 此时 [uKey] 为 `null`
-             */
-            ALREADY_EXISTS,
-            /**
-             * 图片过大. 此时 [uKey] 为 `null`
-             */
-            OVER_FILE_SIZE_MAX,
+        object OverFileSizeMax : Response() {
+            override fun toString(): String = this::class.simpleName!!
         }
     }
 
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): Response = Response().apply {
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): Response {
         discardExact(6)//00 00 00 05 00 00
 
         val length = remaining - 128 - 14
         if (length < 0) {
-            state = if (readUShort().toUInt() == 0x0025u) Response.State.OVER_FILE_SIZE_MAX else Response.State.ALREADY_EXISTS
-            return@apply
+            return if (readUShort().toUInt() == 0x0025u) Response.OverFileSizeMax else Response.AlreadyExists
         }
 
         discardExact(length)
-        uKey = readBytes(128)
-        state = Response.State.REQUIRE_UPLOAD
+        return Response.RequireUpload(readBytes(128))
     }
 }

+ 33 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/AndroidOnlineStatusChange.kt

@@ -0,0 +1,33 @@
+@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
+
+package net.mamoe.mirai.network.protocol.tim.packet.event
+
+import kotlinx.io.core.ByteReadPacket
+import kotlinx.io.core.discardExact
+import net.mamoe.mirai.Bot
+import net.mamoe.mirai.network.protocol.tim.packet.Packet
+import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion
+import net.mamoe.mirai.utils.io.readBoolean
+
+
+@PacketVersion(date = "2019.11.2", timVersion = "2.3.2.21173")
+data class AndroidDeviceStatusChangePacket(val kind: Kind) : Packet {
+    enum class Kind {
+        ONLINE,
+        OFFLINE
+    }
+}
+
+/**
+ * Android 客户端在线状态改变
+ */
+@PacketVersion(date = "2019.10.31", timVersion = "2.3.2.21173")
+object AndroidDeviceOnlineStatusChangedEventFactory : KnownEventParserAndHandler<AndroidDeviceStatusChangePacket>(0x00C4u) {
+    override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): AndroidDeviceStatusChangePacket {
+        discardExact(13)
+        return AndroidDeviceStatusChangePacket(
+            if (readBoolean()) AndroidDeviceStatusChangePacket.Kind.OFFLINE else AndroidDeviceStatusChangePacket.Kind.ONLINE
+        )
+    }
+}
+

+ 6 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/EventPacket.kt

@@ -0,0 +1,6 @@
+package net.mamoe.mirai.network.protocol.tim.packet.event
+
+import net.mamoe.mirai.event.Subscribable
+import net.mamoe.mirai.network.protocol.tim.packet.Packet
+
+interface EventPacket : Subscribable, Packet

+ 106 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/EventPacketFactory.kt

@@ -0,0 +1,106 @@
+@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
+
+package net.mamoe.mirai.network.protocol.tim.packet.event
+
+import kotlinx.io.core.*
+import net.mamoe.mirai.Bot
+import net.mamoe.mirai.network.BotNetworkHandler
+import net.mamoe.mirai.network.protocol.tim.TIMProtocol
+import net.mamoe.mirai.network.protocol.tim.packet.*
+import net.mamoe.mirai.network.sessionKey
+import net.mamoe.mirai.qqAccount
+import net.mamoe.mirai.utils.io.encryptAndWrite
+import net.mamoe.mirai.utils.io.readIoBuffer
+import net.mamoe.mirai.utils.io.writeHex
+import net.mamoe.mirai.utils.io.writeQQ
+
+/**
+ * 事件的识别 ID. 在 ACK 时使用
+ */
+class EventPacketIdentity(
+    val from: UInt,//对于好友消息, 这个是发送人
+    val to: UInt,//对于好友消息, 这个是bot
+    internal val uniqueId: IoBuffer//8
+) {
+    override fun toString(): String = "($from->$to)"
+}
+
+fun BytePacketBuilder.writeEventPacketIdentity(identity: EventPacketIdentity) = with(identity) {
+    writeUInt(from)
+    writeUInt(to)
+    writeFully(uniqueId)
+}
+
+
+@Suppress("FunctionName")
+fun matchEventPacketFactory(value: UShort): EventParserAndHandler<*> =
+    KnownEventParserAndHandler.firstOrNull { it.id == value } ?: IgnoredEventIds.firstOrNull { it.id == value } ?: UnknownEventParserAndHandler(value)
+
+/**
+ * 事件包, 它将会分析事件 ID 并解析事件为 [Packet]
+ */
+@Suppress("FunctionName")
+object EventPacketFactory : PacketFactory<Packet, SessionKey>(SessionKey) {
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): Packet {
+        val eventIdentity = EventPacketIdentity(
+            from = readUInt(),
+            to = readUInt(),
+            uniqueId = readIoBuffer(8)
+        )
+        handler.sendPacket(EventPacketFactory(id, sequenceId, handler.bot.qqAccount, handler.sessionKey, eventIdentity))
+        discardExact(2)
+
+        return with(matchEventPacketFactory(readUShort())) { parse(handler.bot, eventIdentity) }.also {
+            if (it is MessageEventPacket<*>) {
+                it.botVar = handler.bot
+            }
+
+            if (it is EventParserAndHandler<*>) {
+                @Suppress("UNCHECKED_CAST")
+                with(it as EventParserAndHandler<in Packet>) {
+                    with(handler) {
+                        handlePacket(it)
+                    }
+                }
+
+            }
+        }
+    }
+
+
+    operator fun invoke(
+        id: PacketId,
+        sequenceId: UShort,
+        bot: UInt,
+        sessionKey: SessionKey,
+        identity: EventPacketIdentity
+    ): OutgoingPacket = buildOutgoingPacket(name = "EventPacket", id = id, sequenceId = sequenceId) {
+        writeQQ(bot)
+        writeHex(TIMProtocol.fixVer2)
+        encryptAndWrite(sessionKey) {
+            writeEventPacketIdentity(identity)
+        }
+    }
+}
+
+interface EventParserAndHandler<TPacket : Packet> {
+    val id: UShort
+
+    suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): TPacket
+
+    /**
+     * 在 [BotNetworkHandler] 下处理这个包. 广播事件等.
+     */
+    suspend fun BotNetworkHandler<*>.handlePacket(packet: TPacket) {}
+}
+
+abstract class KnownEventParserAndHandler<TPacket : Packet>(override val id: UShort) : EventParserAndHandler<TPacket> {
+    companion object FactoryList : MutableList<KnownEventParserAndHandler<*>> by mutableListOf(
+        AndroidDeviceOnlineStatusChangedEventFactory,
+        FriendConversationInitializedEventParserAndHandler,
+        GroupFileUploadEventFactory,
+        GroupMemberPermissionChangedEventFactory,
+        GroupMessageEventParserAndHandler,
+        FriendMessageEventParserAndHandler
+    )
+}

+ 23 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/FriendConversationIniliaze.kt

@@ -0,0 +1,23 @@
+@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
+
+package net.mamoe.mirai.network.protocol.tim.packet.event
+
+import kotlinx.io.core.ByteReadPacket
+import kotlinx.io.core.discardExact
+import kotlinx.io.core.readUInt
+import net.mamoe.mirai.Bot
+import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion
+
+
+@PacketVersion(date = "2019.11.2", timVersion = "2.3.2.21173")
+data class FriendConversationInitialize(
+    val qq: UInt
+) : EventPacket
+
+object FriendConversationInitializedEventParserAndHandler : KnownEventParserAndHandler<FriendConversationInitialize>(0x0079u) {
+    override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): FriendConversationInitialize {
+        discardExact(4)// 00 00 00 00
+        return FriendConversationInitialize(readUInt())
+    }
+
+}

+ 17 - 18
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/FriendOnlineStatusChanged.kt → mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/FriendOnlineStatusChanged.kt

@@ -1,20 +1,24 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "JoinDeclarationAndAssignment")
 
-package net.mamoe.mirai.network.protocol.tim.packet
+package net.mamoe.mirai.network.protocol.tim.packet.event
 
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.discardExact
 import kotlinx.io.core.readUByte
 import kotlinx.io.core.readUInt
-import net.mamoe.mirai.event.events.FriendOnlineStatusChangedEvent
+import net.mamoe.mirai.contact.QQ
+import net.mamoe.mirai.getQQ
 import net.mamoe.mirai.network.BotNetworkHandler
+import net.mamoe.mirai.network.protocol.tim.packet.AnnotatedId
+import net.mamoe.mirai.network.protocol.tim.packet.KnownPacketId
+import net.mamoe.mirai.network.protocol.tim.packet.PacketId
+import net.mamoe.mirai.network.protocol.tim.packet.SessionPacketFactory
 import net.mamoe.mirai.utils.OnlineStatus
 
-@CorrespondingEvent(FriendOnlineStatusChangedEvent::class)
-abstract class FriendStatusChanged : Packet {
-    abstract val qq: UInt
-    abstract val status: OnlineStatus
-}
+data class FriendStatusChanged(
+    val qq: QQ,
+    val status: OnlineStatus
+) : EventPacket
 
 /**
  * 好友在线状态改变
@@ -22,17 +26,12 @@ abstract class FriendStatusChanged : Packet {
 @AnnotatedId(KnownPacketId.FRIEND_ONLINE_STATUS_CHANGE)
 object FriendOnlineStatusChangedPacket : SessionPacketFactory<FriendStatusChanged>() {
 
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): FriendStatusChanged =
-        object : FriendStatusChanged() {
-            override val qq: UInt
-            override val status: OnlineStatus
-
-            init {
-                qq = readUInt()
-                discardExact(8)
-                val id = readUByte()
-                status = OnlineStatus.ofId(id) ?: error("Unknown online status id $id")
-            }
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): FriendStatusChanged {
+        val qq = readUInt()
+        discardExact(8)
+        val statusId = readUByte()
+        val status = OnlineStatus.ofId(statusId) ?: error("Unknown online status id $statusId")
+        return FriendStatusChanged(handler.bot.getQQ(qq), status)
     }
 
     //在线     XX XX XX XX 01 00 00 00 00 00 00 00 0A 15 E3 10 00 01 2E 01 00 00 00 00 00 00 00 00 00 00 00 13 08 02 C2 76 E4 B8 DD 00 00 00 00 00 00 00 00 00 00 00

+ 22 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/GroupFileUpload.kt

@@ -0,0 +1,22 @@
+@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
+
+package net.mamoe.mirai.network.protocol.tim.packet.event
+
+import kotlinx.io.core.ByteReadPacket
+import kotlinx.io.core.discardExact
+import net.mamoe.mirai.Bot
+import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion
+import net.mamoe.mirai.utils.io.readString
+
+
+data class GroupFileUploadPacket(inline val xmlMessage: String) : EventPacket
+
+@PacketVersion(date = "2019.7.1", timVersion = "2.3.2.21173")
+object GroupFileUploadEventFactory : KnownEventParserAndHandler<GroupFileUploadPacket>(0x002Du) {
+    override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): GroupFileUploadPacket {
+        discardExact(60)
+        val size = readShort().toInt()
+        discardExact(3)
+        return GroupFileUploadPacket(xmlMessage = readString(size))
+    }
+}

+ 18 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/Ignored.kt

@@ -0,0 +1,18 @@
+@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
+
+package net.mamoe.mirai.network.protocol.tim.packet.event
+
+import kotlinx.io.core.ByteReadPacket
+import net.mamoe.mirai.Bot
+
+object IgnoredEventPacket : EventPacket
+
+object IgnoredEventIds : List<IgnoredEventParserAndHandler> by {
+    listOf(
+        0x0021u
+    ).map { IgnoredEventParserAndHandler(it.toUShort()) }
+}()
+
+inline class IgnoredEventParserAndHandler(override val id: UShort) : EventParserAndHandler<IgnoredEventPacket> {
+    override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): IgnoredEventPacket = IgnoredEventPacket
+}

+ 46 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/MemberPermission.kt

@@ -0,0 +1,46 @@
+@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
+
+package net.mamoe.mirai.network.protocol.tim.packet.event
+
+import kotlinx.io.core.ByteReadPacket
+import kotlinx.io.core.discardExact
+import kotlinx.io.core.readUInt
+import net.mamoe.mirai.Bot
+import net.mamoe.mirai.network.protocol.tim.packet.Packet
+import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion
+
+
+data class MemberPermissionChangePacket(
+    val groupId: UInt,
+    val qq: UInt,
+    val kind: Kind
+) : Packet {
+    enum class Kind {
+        /**
+         * 变成管理员
+         */
+        BECOME_OPERATOR,
+        /**
+         * 不再是管理员
+         */
+        NO_LONGER_OPERATOR,
+    } // TODO: 2019/11/2 变成群主的情况
+}
+
+@PacketVersion(date = "2019.11.1", timVersion = "2.3.2.21173")
+object GroupMemberPermissionChangedEventFactory : KnownEventParserAndHandler<MemberPermissionChangePacket>(0x002Cu) {
+    override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): MemberPermissionChangePacket {
+        // 群里一个人变成管理员:
+        // 00 00 00 08 00 0A 00 04 01 00 00 00 22 96 29 7B 01 01 76 E4 B8 DD 01
+        // 取消管理员
+        // 00 00 00 08 00 0A 00 04 01 00 00 00 22 96 29 7B 01 00 76 E4 B8 DD 00
+        discardExact(remaining - 5)
+        val qq = readUInt()
+        val kind = when (readByte().toInt()) {
+            0x00 -> MemberPermissionChangePacket.Kind.NO_LONGER_OPERATOR
+            0x01 -> MemberPermissionChangePacket.Kind.BECOME_OPERATOR
+            else -> error("Could not determine permission change kind")
+        }
+        return MemberPermissionChangePacket(identity.from, qq, kind)
+    }
+}

+ 173 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/Message.kt

@@ -0,0 +1,173 @@
+@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
+
+package net.mamoe.mirai.network.protocol.tim.packet.event
+
+import kotlinx.io.core.ByteReadPacket
+import kotlinx.io.core.discardExact
+import kotlinx.io.core.readUInt
+import net.mamoe.mirai.Bot
+import net.mamoe.mirai.contact.Contact
+import net.mamoe.mirai.contact.Group
+import net.mamoe.mirai.contact.MemberPermission
+import net.mamoe.mirai.contact.QQ
+import net.mamoe.mirai.event.events.BotEvent
+import net.mamoe.mirai.getGroup
+import net.mamoe.mirai.getQQ
+import net.mamoe.mirai.message.*
+import net.mamoe.mirai.message.internal.readMessageChain
+import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion
+import net.mamoe.mirai.utils.ExternalImage
+import net.mamoe.mirai.utils.MiraiLogger
+import net.mamoe.mirai.utils.io.printTLVMap
+import net.mamoe.mirai.utils.io.read
+import net.mamoe.mirai.utils.io.readTLVMap
+import net.mamoe.mirai.utils.io.readUShortLVByteArray
+import net.mamoe.mirai.utils.sendTo
+import net.mamoe.mirai.utils.upload
+
+sealed class MessageEventPacket<TSubject : Contact> : EventPacket, BotEvent() {
+    internal lateinit var botVar: Bot
+
+    override val bot: Bot get() = botVar
+
+    /**
+     * 消息事件主体.
+     *
+     * 对于好友消息, 这个属性为 [QQ] 的实例;
+     * 对于群消息, 这个属性为 [Group] 的实例
+     *
+     * 在回复消息时, 可通过 [subject] 作为回复对象
+     */
+    abstract val subject: TSubject
+
+    /**
+     * 发送人
+     */
+    abstract val sender: QQ
+
+    abstract val message: MessageChain
+
+
+    // region Send to subject
+
+    /**
+     * 给这个消息事件的主体发送消息
+     * 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息
+     * 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息
+     */
+    suspend inline fun reply(message: MessageChain) = subject.sendMessage(message)
+
+    suspend fun reply(message: Message) = subject.sendMessage(message.singleChain())
+    suspend fun reply(plain: String) = subject.sendMessage(plain.toMessage())
+
+    suspend inline fun ExternalImage.send() = this.sendTo(subject)
+
+    suspend inline fun ExternalImage.upload(): Image = this.upload(subject)
+    suspend inline fun Image.send() = this.sendTo(subject)
+    suspend inline fun ImageId.send() = this.sendTo(subject)
+    suspend inline fun Message.send() = this.sendTo(subject)
+    suspend inline fun String.send() = this.toMessage().sendTo(subject)
+
+    // endregion
+}
+
+// region group message
+
+data class GroupMessage(
+    val group: Group,
+    val senderName: String,
+    /**
+     * 发送方权限.
+     */
+    val permission: MemberPermission,
+    override val sender: QQ,
+    override val message: MessageChain = NullMessageChain
+) : MessageEventPacket<Group>() {
+    override val subject: Group get() = group
+}
+
+@PacketVersion(date = "2019.11.2", timVersion = "2.3.2.21173")
+object GroupMessageEventParserAndHandler : KnownEventParserAndHandler<GroupMessage>(0x0052u) {
+    override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): GroupMessage {
+        discardExact(31)
+        val groupNumber = readUInt()
+        discardExact(1)
+        val qq = readUInt()
+
+        discardExact(48)
+        readUShortLVByteArray()
+        discardExact(2)//2个0x00
+        val message = readMessageChain()
+
+        var senderPermission: MemberPermission = MemberPermission.MEMBER
+        var senderName = ""
+        val map = readTLVMap(true)
+        if (map.containsKey(18u)) {
+            map.getValue(18u).read {
+                val tlv = readTLVMap(true)
+                senderPermission = when (tlv.takeIf { it.containsKey(0x04u) }?.get(0x04u)?.getOrNull(3)?.toUInt()) {
+                    null -> MemberPermission.MEMBER
+                    0x08u -> MemberPermission.OWNER
+                    0x10u -> MemberPermission.OPERATOR
+                    else -> {
+                        tlv.printTLVMap("TLV(tag=18) Map")
+                        MiraiLogger.warning("Could not determine member permission, default permission MEMBER is being used")
+                        MemberPermission.MEMBER
+                    }
+                }
+
+                senderName = when {
+                    tlv.containsKey(0x01u) -> kotlinx.io.core.String(tlv.getValue(0x01u))//这个人的qq昵称
+                    tlv.containsKey(0x02u) -> kotlinx.io.core.String(tlv.getValue(0x02u))//这个人的群名片
+                    else -> {
+                        tlv.printTLVMap("TLV(tag=18) Map")
+                        MiraiLogger.warning("Could not determine senderName")
+                        "null"
+                    }
+                }
+            }
+        }
+
+        return GroupMessage(bot.getGroup(groupNumber), senderName, senderPermission, bot.getQQ(qq), message)
+    }
+}
+
+// endregion
+
+// region friend message
+
+data class FriendMessage(
+    /**
+     * 是否是在这次登录之前的消息, 即消息记录
+     */
+    val isPrevious: Boolean,
+    override val sender: QQ,
+    override val message: MessageChain
+) : MessageEventPacket<QQ>() {
+    override val subject: QQ get() = sender
+}
+
+
+@Suppress("unused")
+@PacketVersion(date = "2019.11.2", timVersion = "2.3.2.21173")
+object FriendMessageEventParserAndHandler : KnownEventParserAndHandler<FriendMessage>(0x00A6u) {
+    override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): FriendMessage {
+        discardExact(2)
+        val l1 = readShort()
+        discardExact(1)//0x00
+        val previous = readByte().toInt() == 0x08
+        discardExact(l1.toInt() - 2)
+        //java.io.EOFException: Only 49 bytes were discarded of 69 requested
+        //抖动窗口消息
+        discardExact(69)
+        readUShortLVByteArray()//font
+        discardExact(2)//2个0x00
+        val message = readMessageChain()
+        return FriendMessage(
+            isPrevious = previous,
+            sender = bot.getQQ(identity.from),
+            message = message
+        )
+    }
+}
+// endregion

+ 0 - 237
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/ServerEventPackets.kt

@@ -1,237 +0,0 @@
-@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
-
-package net.mamoe.mirai.network.protocol.tim.packet.event
-
-import kotlinx.io.core.*
-import net.mamoe.mirai.event.events.MemberPermissionChangedEvent
-import net.mamoe.mirai.message.internal.readMessageChain
-import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.protocol.tim.TIMProtocol
-import net.mamoe.mirai.network.protocol.tim.packet.*
-import net.mamoe.mirai.network.sessionKey
-import net.mamoe.mirai.qqAccount
-import net.mamoe.mirai.utils.MiraiLogger
-import net.mamoe.mirai.utils.io.*
-
-/**
- * 事件的识别 ID. 在 [事件确认包][ServerEventPacket.EventResponse] 中被使用.
- */
-class EventPacketIdentity(
-    val from: UInt,//对于好友消息, 这个是发送人
-    val to: UInt,//对于好友消息, 这个是bot
-    internal val uniqueId: IoBuffer//8
-) {
-    override fun toString(): String = "($from->$to)"
-}
-
-enum class SenderPermission {
-    OWNER,
-    OPERATOR,
-    MEMBER;
-}
-
-object IgnoredEventPacket : Packet
-
-class UnknownEventPacket(
-    val id: UnknownEventId
-// TODO: 2019/11/5  补充包数据 , 用于输出
-) : Packet
-
-/**
- * 事件包, 它将会分析 [事件ID][KnownEventId] 并解析事件为 [Packet]
- */
-@NoLog
-@Suppress("FunctionName")
-object EventPacketFactory : PacketFactory<Packet, SessionKey>(SessionKey) {
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): Packet {
-        val eventIdentity = EventPacketIdentity(
-            from = readUInt(),
-            to = readUInt(),
-            uniqueId = readIoBuffer(8)
-        )
-        handler.sendPacket(EventPacketFactory(id, sequenceId, handler.bot.qqAccount, handler.sessionKey, eventIdentity))
-        discardExact(2)
-        return when (val type = EventId(readUShort())) {
-            is KnownEventId -> type.parser(this, eventIdentity)
-            is UnknownEventId -> UnknownEventPacket(type)
-            is IgnoredEventId -> IgnoredEventPacket
-            else -> throw AssertionError("Unknown EventId type")
-        }
-    }
-
-    operator fun invoke(
-        id: PacketId,
-        sequenceId: UShort,
-        bot: UInt,
-        sessionKey: SessionKey,
-        identity: EventPacketIdentity
-    ): OutgoingPacket = buildOutgoingPacket(name = "EventPacket", id = id, sequenceId = sequenceId) {
-        writeQQ(bot)
-        writeHex(TIMProtocol.fixVer2)
-        encryptAndWrite(sessionKey) {
-            writeEventPacketIdentity(identity)
-        }
-    }
-}
-
-fun BytePacketBuilder.writeEventPacketIdentity(identity: EventPacketIdentity) = with(identity) {
-    writeUInt(from)
-    writeUInt(to)
-    writeFully(uniqueId)
-}
-
-typealias EventPacketParser = ByteReadPacket.(EventPacketIdentity) -> Packet
-
-interface EventId {
-    val value: UShort
-    val parser: EventPacketParser?
-}
-
-
-// TODO: 2019/11/5 整理文件
-
-@Suppress("FunctionName")
-fun EventId(value: UShort): EventId =
-    KnownEventId.ofValueOrNull(value) ?: IgnoredEventIds.firstOrNull { it.value == value } ?: UnknownEventId(value)
-
-
-object IgnoredEventIds : List<IgnoredEventId> by {
-    listOf(
-        0x0021u
-    ).map { IgnoredEventId(it.toUShort()) }
-}()
-
-inline class IgnoredEventId(override val value: UShort) : EventId {
-    override val parser: EventPacketParser get() = { IgnoredPacket }
-}
-
-inline class UnknownEventId(override val value: UShort) : EventId {
-    override val parser: EventPacketParser
-        get() = {
-            MiraiLogger.debug("UnknownEventPacket type = ${value.toUHexString()}")
-            MiraiLogger.debug("UnknownEventPacket data = ${readBytes().toUHexString()}")
-            UnknownEventPacket(UnknownEventId(value))
-        }
-}
-
-/**
- *
- * @param parser 解析器. 解析 [数据包][ByteReadPacket] 为 [Packet]
- */
-@Suppress("unused")
-enum class KnownEventId(override inline val value: UShort, override val parser: EventPacketParser) : EventId {
-    /**
-     * Android 客户端在线状态改变
-     */
-    ANDROID_DEVICE_ONLINE_STATUS_CHANGE(0x00C4u, {
-        discardExact(13)
-        EventPacket.AndroidDeviceStatusChange(
-            if (readBoolean()) EventPacket.AndroidDeviceStatusChange.Kind.OFFLINE else EventPacket.AndroidDeviceStatusChange.Kind.ONLINE
-        )
-    }),
-
-
-    GROUP_FILE_UPLOAD(0x002Du, {
-        discardExact(60)
-        val size = readShort().toInt()
-        discardExact(3)
-        EventPacket.GroupFileUpload(xmlMessage = readString(size))
-    }),
-
-    @PacketVersion(date = "2019.11.2", timVersion = "2.3.2.21173")
-    GROUP_MEMBER_PERMISSION_CHANGE(0x002Cu, {
-        // 群里一个人变成管理员:
-        // 00 00 00 08 00 0A 00 04 01 00 00 00 22 96 29 7B 01 01 76 E4 B8 DD 01
-        // 取消管理员
-        // 00 00 00 08 00 0A 00 04 01 00 00 00 22 96 29 7B 01 00 76 E4 B8 DD 00
-        discardExact(remaining - 5)
-        EventPacket.MemberPermissionChange().apply {
-            groupId = it.from
-            qq = readUInt()
-            kind = when (readByte().toInt()) {
-                0x00 -> MemberPermissionChangedEvent.Kind.NO_LONGER_OPERATOR
-                0x01 -> MemberPermissionChangedEvent.Kind.BECOME_OPERATOR
-                else -> error("Could not determine permission change kind")
-            }
-        }
-    }),
-
-
-    @PacketVersion(date = "2019.11.2", timVersion = "2.3.2.21173")
-    GROUP_MESSAGE(0x0052u, {
-        EventPacket.GroupMessage().apply {
-            discardExact(31)
-            groupNumber = readUInt()
-            discardExact(1)
-            qq = readUInt()
-
-            discardExact(48)
-            readUShortLVByteArray()
-            discardExact(2)//2个0x00
-            message = readMessageChain()
-
-            val map = readTLVMap(true)
-            if (map.containsKey(18u)) {
-                map.getValue(18u).read {
-                    val tlv = readTLVMap(true)
-                    senderPermission = when (tlv.takeIf { it.containsKey(0x04u) }?.get(0x04u)?.getOrNull(3)?.toUInt()) {
-                        null -> SenderPermission.MEMBER
-                        0x08u -> SenderPermission.OWNER
-                        0x10u -> SenderPermission.OPERATOR
-                        else -> {
-                            tlv.printTLVMap("TLV(tag=18) Map")
-                            MiraiLogger.warning("Could not determine member permission, default permission MEMBER is being used")
-                            SenderPermission.MEMBER
-                        }
-                    }
-
-                    senderName = when {
-                        tlv.containsKey(0x01u) -> String(tlv.getValue(0x01u))//这个人的qq昵称
-                        tlv.containsKey(0x02u) -> String(tlv.getValue(0x02u))//这个人的群名片
-                        else -> {
-                            tlv.printTLVMap("TLV(tag=18) Map")
-                            MiraiLogger.warning("Could not determine senderName")
-                            "null"
-                        }
-                    }
-                }
-            }
-
-        }
-    }),
-
-
-    @PacketVersion(date = "2019.11.2", timVersion = "2.3.2.21173")
-    FRIEND_MESSAGE(0x00A6u, {
-        discardExact(2)
-        val l1 = readShort()
-        discardExact(1)//0x00
-        val previous = readByte().toInt() == 0x08
-        discardExact(l1.toInt() - 2)
-        //java.io.EOFException: Only 49 bytes were discarded of 69 requested
-        //抖动窗口消息
-        discardExact(69)
-        readUShortLVByteArray()//font
-        discardExact(2)//2个0x00
-        EventPacket.FriendMessage(
-            isPrevious = previous,
-            qq = it.from,
-            message = readMessageChain()
-        )
-    }),
-
-
-    FRIEND_CONVERSATION_INITIALIZE(0x0079u, {
-        discardExact(4)// 00 00 00 00
-        EventPacket.FriendConversationInitialize().apply {
-            qq = readUInt()
-        }
-    }),
-
-
-    ;
-
-    companion object {
-        fun ofValueOrNull(value: UShort): KnownEventId? = values().firstOrNull { it.value == value }
-    }
-}

+ 34 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/Unknown.kt

@@ -0,0 +1,34 @@
+@file:Suppress("EXPERIMENTAL_API_USAGE")
+
+package net.mamoe.mirai.network.protocol.tim.packet.event
+
+import kotlinx.io.core.ByteReadPacket
+import kotlinx.io.core.readBytes
+import kotlinx.io.pool.useInstance
+import net.mamoe.mirai.Bot
+import net.mamoe.mirai.network.BotNetworkHandler
+import net.mamoe.mirai.utils.MiraiLogger
+import net.mamoe.mirai.utils.io.ByteArrayPool
+import net.mamoe.mirai.utils.io.toUHexString
+
+data class UnknownEventPacket(
+    val id: UShort,
+    val body: ByteReadPacket
+) : EventPacket
+
+//TODO This class should be declared with `inline`, but a CompilationException will be thrown
+class UnknownEventParserAndHandler(override val id: UShort) : EventParserAndHandler<UnknownEventPacket> {
+
+    override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): UnknownEventPacket {
+        MiraiLogger.debug("UnknownEventPacket type = ${id.toUHexString()}")
+        MiraiLogger.debug("UnknownEventPacket data = ${readBytes().toUHexString()}")
+        return UnknownEventPacket(id, this) //TODO the cause is that `this` reference.
+    }
+
+    override suspend fun BotNetworkHandler<*>.handlePacket(packet: UnknownEventPacket) {
+        ByteArrayPool.useInstance {
+            packet.body.readAvailable(it)
+            bot.logger.debug("Unknown packet(${packet.id}) data = " + it.toUHexString())
+        }
+    }
+}

+ 2 - 2
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Captcha.kt

@@ -110,7 +110,7 @@ object CaptchaPacket : PacketFactory<CaptchaPacket.CaptchaResponse, CaptchaKey>(
     }
 
     override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): CaptchaResponse =
-        when (val id = readByte().toUInt()) {
+        when (val flag = readByte().toUInt()) {
             0x14u -> {//00 05 00 00 00 00 00 00 38
                 CaptchaResponse.Correct().apply {
                     discardExact(9)
@@ -134,7 +134,7 @@ object CaptchaPacket : PacketFactory<CaptchaPacket.CaptchaResponse, CaptchaKey>(
                 }
             }
 
-            else -> error("Unable to analyze RequestCaptchaTransmissionPacket, unknown id: $id")
+            else -> error("Unable to analyze RequestCaptchaTransmissionPacket, unknown id: $flag")
         }
 }
 /*

+ 3 - 1
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/ChangeOnlineStatusPacket.kt

@@ -31,7 +31,9 @@ object ChangeOnlineStatusPacket : PacketFactory<ChangeOnlineStatusPacket.ChangeO
         }
     }
 
-    object ChangeOnlineStatusResponse : Packet
+    object ChangeOnlineStatusResponse : Packet {
+        override fun toString(): String = this::class.simpleName!!
+    }
 
     override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): ChangeOnlineStatusResponse =
         ChangeOnlineStatusResponse

+ 0 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/Heartbeat.kt → mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Heartbeat.kt


+ 86 - 39
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/PasswordSubmission.kt

@@ -12,7 +12,6 @@ import net.mamoe.mirai.utils.decryptBy
 import net.mamoe.mirai.utils.encryptBy
 import net.mamoe.mirai.utils.io.*
 import net.mamoe.mirai.utils.writeCRC32
-import kotlin.properties.Delegates
 
 object ShareKey : DecrypterByteArray, DecrypterType<ShareKey> {
     override val value: ByteArray = TIMProtocol.shareKey.hexToBytes(withCache = false)
@@ -27,9 +26,14 @@ inline class SubmitPasswordResponseDecrypter(private val privateKey: PrivateKey)
         var decrypted = ShareKey.decrypt(packet)
         (decrypted.remaining).let {
             if (it.toInt() % 8 == 0 && it >= 16) {
-                decrypted = privateKey.decrypt(decrypted)
+                decrypted = try {
+                    privateKey.decrypt(decrypted)
+                } catch (e: Exception) {
+                    // 某些情况不需要这次解密
+                    decrypted
+                }
             }
-        } // TODO: 2019/11/5 优化: 某些情况下并不需要这次解密. 根据长度判断会导致一些问题
+        }
 
         return decrypted
     }
@@ -59,7 +63,7 @@ object SubmitPasswordPacket : PacketFactory<SubmitPasswordPacket.LoginResponse,
         writeZero(2)
         writeShort(16); writeHex(TIMProtocol.key0836)//=16
 
-        //TODO shareKey 极大可能为 publicKey, key0836 计算得到
+        // shareKey 极大可能为 publicKey, key0836 计算得到
         encryptAndWrite(TIMProtocol.shareKey) {
             writePart1(bot, password, loginTime, loginIP, privateKey, token0825, randomDeviceName, tlv0006)
             if (token00BA != null) {
@@ -73,71 +77,112 @@ object SubmitPasswordPacket : PacketFactory<SubmitPasswordPacket.LoginResponse,
     }
 
     sealed class LoginResponse : Packet {
-        class KeyExchange : LoginResponse() {
-            lateinit var tlv0006: IoBuffer//120bytes
-            var tokenUnknown: ByteArray? = null
+        data class KeyExchange(
+            val tlv0006: IoBuffer,//120bytes
+            val tokenUnknown: ByteArray?,
+
+            val privateKeyUpdate: PrivateKey//16bytes
+        ) : LoginResponse() {
+            override fun equals(other: Any?): Boolean {
+                if (this === other) return true
+                if (other !is KeyExchange) return false
+
+                if (tlv0006 != other.tlv0006) return false
+                if (tokenUnknown != null) {
+                    if (other.tokenUnknown == null) return false
+                    if (!tokenUnknown.contentEquals(other.tokenUnknown)) return false
+                } else if (other.tokenUnknown != null) return false
+                if (privateKeyUpdate != other.privateKeyUpdate) return false
+
+                return true
+            }
 
-            var privateKeyUpdate: PrivateKey? = null//16bytes
+            override fun hashCode(): Int {
+                var result = tlv0006.hashCode()
+                result = 31 * result + (tokenUnknown?.contentHashCode() ?: 0)
+                result = 31 * result + privateKeyUpdate.hashCode()
+                return result
+            }
         }
 
-        class CaptchaInit : LoginResponse() {
-            lateinit var captchaPart1: IoBuffer
-            lateinit var token00BA: ByteArray
-            var unknownBoolean: Boolean by Delegates.notNull()
-        }
+        data class CaptchaInit(
+            val captchaPart1: IoBuffer,
+            val token00BA: ByteArray,
+            val unknownBoolean: Boolean
+        ) : LoginResponse() {
+            override fun equals(other: Any?): Boolean {
+                if (this === other) return true
+                if (other !is CaptchaInit) return false
 
-        class Success : LoginResponse() {
-            var sessionResponseDecryptionKey: SessionResponseDecryptionKey by Delegates.notNull()//16 bytes|
+                if (captchaPart1 != other.captchaPart1) return false
+                if (!token00BA.contentEquals(other.token00BA)) return false
+                if (unknownBoolean != other.unknownBoolean) return false
 
-            lateinit var token38: IoBuffer//56
-            lateinit var token88: IoBuffer//136
-            lateinit var encryptionKey: IoBuffer//16
+                return true
+            }
 
-            lateinit var nickname: String
-            var age: Short by Delegates.notNull()
-            lateinit var gender: Gender
+            override fun hashCode(): Int {
+                var result = captchaPart1.hashCode()
+                result = 31 * result + token00BA.contentHashCode()
+                result = 31 * result + unknownBoolean.hashCode()
+                return result
+            }
         }
 
-        class Failed(val result: LoginResult) : LoginResponse()
+        data class Success(
+            val sessionResponseDecryptionKey: SessionResponseDecryptionKey,
+
+            val token38: IoBuffer,//56
+            val token88: IoBuffer,//136
+            val encryptionKey: IoBuffer,//16
+
+            val nickname: String,
+            val age: Short,
+            val gender: Gender
+        ) : LoginResponse()
+
+        data class Failed(val result: LoginResult) : LoginResponse()
     }
 
     override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): LoginResponse {
         val size = remaining.toInt()
         return when {
-            size == 229 || size == 271 || size == 207 -> LoginResponse.KeyExchange().apply {
+            size == 229 || size == 271 || size == 207 -> {
                 discardExact(5)//01 00 1E 00 10
-                privateKeyUpdate = PrivateKey(readBytes(0x10))
+                val privateKeyUpdate = PrivateKey(readBytes(0x10))
                 discardExact(4)//00 06 00 78
-                tlv0006 = readIoBuffer(0x78)
+                val tlv0006 = readIoBuffer(0x78)
 
-                try {
+                return try {
                     discardExact(8)//01 10 00 3C 00 01 00 38
-                    tokenUnknown = readBytes(56)
+                    LoginResponse.KeyExchange(tlv0006, readBytes(56), privateKeyUpdate)
                 } catch (e: EOFException) {
                     //什么都不做. 因为有的包就是没有这个数据.
+                    LoginResponse.KeyExchange(tlv0006, null, privateKeyUpdate)
                 }
             }
 
-            size == 844 || size == 871 -> LoginResponse.CaptchaInit().apply {
+            size == 844 || size == 871 -> {
                 discardExact(78)
                 //println(readRemainingBytes().toUHexString())
                 val captchaLength = readShort()//2bytes
-                this.captchaPart1 = readIoBuffer(captchaLength)
+                val captchaPart1 = readIoBuffer(captchaLength)
 
                 discardExact(1)
 
-                this.unknownBoolean = readByte().toInt() == 1
+                val unknownBoolean = readByte().toInt() == 1
 
                 discardExact(remaining - 60)
-                this.token00BA = readBytes(40)
+
+                return LoginResponse.CaptchaInit(captchaPart1, readBytes(40), unknownBoolean)
             }
-            size > 650 -> LoginResponse.Success().apply {
+            size > 650 -> {
                 discardExact(7)//00 01 09 00 70 00 01
                 //FB 01 04 03 33
-                encryptionKey = readIoBuffer(16)//C6 72 C7 73 70 01 46 A2 11 88 AC E4 92 7B BF 90
+                val encryptionKey = readIoBuffer(16)//C6 72 C7 73 70 01 46 A2 11 88 AC E4 92 7B BF 90
 
                 discardExact(2)//00 38
-                token38 = readIoBuffer(56)
+                val token38 = readIoBuffer(56)
 
                 discardExact(60)//00 20 01 60 C5 A1 39 7A 12 8E BC 34 C3 56 70 E3 1A ED 20 67 ED A9 DB 06 C1 70 81 3C 01 69 0D FF 63 DA 00 00 01 03 00 14 00 01 00 10 60 C9 5D A7 45 70 04 7F 21 7D 84 50 5C 66 A5 C6
 
@@ -159,24 +204,26 @@ object SubmitPasswordPacket : PacketFactory<SubmitPasswordPacket.LoginResponse,
                 discardExact(23 + 3)//01 D3 00 01 00 16 00 00 00 01 00 00 00 64 00 00 0D DE 00 09 3A 80 00
 
                 discardExact(2)//00 02
-                sessionResponseDecryptionKey = SessionResponseDecryptionKey(readIoBuffer(16))
+                val sessionResponseDecryptionKey = SessionResponseDecryptionKey(readIoBuffer(16))
                 discardExact(2)
-                token88 = readIoBuffer(136)
+                val token88 = readIoBuffer(136)
 
                 discardExact(299)//2E 72 7A 50 41 54 5B 62 7D 47 5D 37 41 53 47 51 00 78 00 01 5D A2 DB 79 00 70 72 E7 D3 4E 6F D8 D1 DD F2 67 04 1D 23 4D E9 A7 AB 89 7A B7 E6 4B C0 79 60 3B 4F AA 31 C5 24 51 C1 4B 4F A4 32 74 BA FE 8E 06 DB 54 25 A2 56 91 E8 66 BB 23 29 EB F7 13 7B 94 1E AF B2 40 4E 69 5C 8C 35 04 D1 25 1F 60 93 F3 40 71 0B 61 60 F1 B6 A9 7A E8 B1 DA 0E 16 A2 F1 2D 69 5A 01 20 7A AB A7 37 68 D2 1A B0 4D 35 D1 E1 35 64 F6 90 2B 00 83 01 24 5B 4E 69 3D 45 54 6B 29 5E 73 23 2D 4E 42 3F 00 70 00 01 5D A2 DB 79 00 68 FD 10 8A 39 51 09 C6 69 CE 09 A4 52 8C 53 D3 B6 87 E1 7B 7E 4E 52 6D BA 9C C4 6E 6D DE 09 99 67 B4 BD 56 71 14 5A 54 01 68 1C 3C AA 0D 76 0B 86 5A C1 F1 BC 5E 0A ED E3 8C 57 86 35 D8 A5 F8 16 01 24 8B 57 56 8C A6 31 6F 65 73 03 DA ED 21 FA 6B 79 32 2B 09 01 E8 D2 D8 F0 7B F1 60 C2 7F 53 5D F6 53 50 8A 43 E2 23 2E 52 7B 60 39 56 67 2D 6A 23 43 4B 60 55 68 35 01 08 00 23 00 01 00 1F 00 17 02 5B
                 val nickLength = readUByte().toInt()
-                nickname = readString(nickLength)
+                val nickname = readString(nickLength)
 
                 //后文
                 //00 05 00 04 00 00 00 01 01 15 00 10 49 83 5C D9 93 6C 8D FE 09 18 99 37 99 80 68 92
 
                 discardExact(4)//02 13 80 02
-                age = readShort()//00 05
+                val age = readShort()//00 05
 
                 discardExact(4)//00 04 00 00
 
                 discardExact(2)//00 01
-                gender = if (readBoolean()) Gender.FEMALE else Gender.MALE
+                val gender = if (readBoolean()) Gender.FEMALE else Gender.MALE
+
+                return LoginResponse.Success(sessionResponseDecryptionKey, token38, token88, encryptionKey, nickname, age, gender)
             }
 
             else -> LoginResponse.Failed(when (size) {

+ 2 - 2
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Touch.kt

@@ -57,7 +57,7 @@ object TouchPacket : PacketFactory<TouchPacket.TouchResponse, TouchKey>(TouchKey
     }
 
     override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): TouchResponse = TouchResponse().apply {
-        when (val id = readByte().toUByte().toInt()) {
+        when (val flag = readByte().toUByte().toInt()) {
             0xFE -> {
                 discardExact(94)
                 serverIP = readIP()
@@ -71,7 +71,7 @@ object TouchPacket : PacketFactory<TouchPacket.TouchResponse, TouchKey>(TouchKey
                 loginIP = readIP()
             }
 
-            else -> throw IllegalStateException(id.toByte().toUHexString())
+            else -> throw IllegalStateException(flag.toByte().toUHexString())
         }
     }
 }

+ 6 - 3
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Calculation.kt

@@ -19,11 +19,14 @@ internal fun getGTK(sKey: String): Int {
 }
 
 @Tested
-fun BytePacketBuilder.writeCRC32() = writeCRC32(getRandomByteArray(16))
+@PublishedApi
+internal fun BytePacketBuilder.writeCRC32() = writeCRC32(getRandomByteArray(16))
 
-fun BytePacketBuilder.writeCRC32(key: ByteArray) {
+@PublishedApi
+internal fun BytePacketBuilder.writeCRC32(key: ByteArray) {
     writeFully(key)//key
     writeInt(crc32(key))
 }
 
-fun md5(str: String): ByteArray = md5(str.toByteArray())
+@PublishedApi
+internal fun md5(str: String): ByteArray = md5(str.toByteArray())

+ 3 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt

@@ -25,6 +25,9 @@ fun ExternalImage(
 
 /**
  * 外部图片. 图片数据还没有读取到内存.
+ *
+ * 在 JVM, 请查看 'ExternalImageJvm.kt'
+ *
  * @see ExternalImage.sendTo 上传图片并以纯图片消息发送给联系人
  * @See ExternalImage.upload 上传图片并得到 [Image] 消息
  */

+ 2 - 2
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/MiraiLogger.kt

@@ -17,7 +17,7 @@ var DefaultLogger: (identity: String?) -> MiraiLogger = { PlatformLogger() }
  *
  * 不应该直接构造这个类的实例. 请使用 [DefaultLogger]
  */
-expect open class PlatformLogger @JvmOverloads internal constructor(identity: String? = null) : MiraiLoggerPlatformBase
+expect open class PlatformLogger @JvmOverloads internal constructor(identity: String? = "Mirai") : MiraiLoggerPlatformBase
 
 
 /**
@@ -32,7 +32,7 @@ interface MiraiLogger {
     /**
      * 顶层日志记录器
      */
-    companion object : MiraiLogger by DefaultLogger("TOP Level")
+    companion object : MiraiLogger by DefaultLogger("Mirai")
 
     val identity: String?
 

+ 0 - 2
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/PlatformLogger.kt

@@ -1,2 +0,0 @@
-package net.mamoe.mirai.utils 
-

+ 3 - 2
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/SuspendLazy.kt

@@ -11,7 +11,7 @@ import net.mamoe.mirai.contact.QQ
 /**
  * 创建一个在当前 [CoroutineScope] 下执行的 [SuspendLazy]
  */
-fun <R> CoroutineScope.SuspendLazy(initializer: suspend () -> R): SuspendLazy<R> = SuspendLazy(this, initializer)
+fun <R> CoroutineScope.SuspendLazy(initializer: suspend () -> R): Lazy<Deferred<R>> = SuspendLazy(this, initializer)
 
 /**
  * 挂起初始化的 [lazy], 是属性不能 `suspend` 的替代品.
@@ -26,7 +26,8 @@ fun <R> CoroutineScope.SuspendLazy(initializer: suspend () -> R): SuspendLazy<R>
  *
  * @sample QQ.profile
  */
-class SuspendLazy<R>(scope: CoroutineScope, val initializer: suspend () -> R) : Lazy<Deferred<R>> {
+@PublishedApi
+internal class SuspendLazy<R>(scope: CoroutineScope, val initializer: suspend () -> R) : Lazy<Deferred<R>> {
     private val valueUpdater: Deferred<R> by lazy { scope.async { initializer() } }
 
     @Suppress("EXPERIMENTAL_API_USAGE")

+ 51 - 14
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/TEA.kt

@@ -25,9 +25,11 @@ class DecryptionFailedException : Exception()
  * @param key 长度至少为 16
  * @throws DecryptionFailedException 解密错误时
  */
-fun ByteArray.encryptBy(key: ByteArray, length: Int = this.size): ByteArray = TEA.encrypt(this, key, sourceLength = length)
+@PublishedApi
+internal fun ByteArray.encryptBy(key: ByteArray, length: Int = this.size): ByteArray = TEA.encrypt(this, key, sourceLength = length)
 
-fun ByteArray.encryptBy(key: DecrypterByteArray, length: Int = this.size): ByteArray = TEA.encrypt(this, key.value, sourceLength = length)
+@PublishedApi
+internal fun ByteArray.encryptBy(key: DecrypterByteArray, length: Int = this.size): ByteArray = TEA.encrypt(this, key.value, sourceLength = length)
 
 /**
  * 通过 [String.hexToBytes] 将 [keyHex] 转为 [ByteArray] 后用它解密 [this].
@@ -36,7 +38,8 @@ fun ByteArray.encryptBy(key: DecrypterByteArray, length: Int = this.size): ByteA
  * @param keyHex 长度至少为 16 bytes
  * @throws DecryptionFailedException 解密错误时
  */
-fun ByteArray.encryptBy(keyHex: String, length: Int = this.size): ByteArray = encryptBy(keyHex.hexToBytes(withCache = true), length = length)
+@PublishedApi
+internal fun ByteArray.encryptBy(keyHex: String, length: Int = this.size): ByteArray = encryptBy(keyHex.hexToBytes(withCache = true), length = length)
 
 /**
  * 在 [ByteArrayPool] 缓存 [this], 然后使用 [key] 加密.
@@ -45,7 +48,8 @@ fun ByteArray.encryptBy(keyHex: String, length: Int = this.size): ByteArray = en
  * @consumer 由于缓存需要被回收, 需在方法内执行解密后明文的消耗过程
  * @throws DecryptionFailedException 解密错误时
  */
-inline fun ByteReadPacket.encryptBy(key: ByteArray, offset: Int = 0, length: Int = remaining.toInt() - offset, consumer: (ByteArray) -> Unit) {
+@PublishedApi
+internal inline fun ByteReadPacket.encryptBy(key: ByteArray, offset: Int = 0, length: Int = remaining.toInt() - offset, consumer: (ByteArray) -> Unit) {
     ByteArrayPool.useInstance {
         this.readFully(it, offset, length)
         consumer(it.encryptBy(key, length = length))
@@ -63,7 +67,9 @@ inline fun ByteReadPacket.encryptBy(key: ByteArray, offset: Int = 0, length: Int
  * @param key 固定长度 16
  * @throws DecryptionFailedException 解密错误时
  */
-fun ByteArray.decryptBy(key: ByteArray, length: Int = this.size): ByteArray = TEA.decrypt(checkDataLengthAndReturnSelf(length), key, sourceLength = length)
+@PublishedApi
+internal fun ByteArray.decryptBy(key: ByteArray, length: Int = this.size): ByteArray =
+    TEA.decrypt(checkDataLengthAndReturnSelf(length), key, sourceLength = length)
 
 /**
  * 使用 [key] 解密 [this].
@@ -73,7 +79,8 @@ fun ByteArray.decryptBy(key: ByteArray, length: Int = this.size): ByteArray = TE
  * @param key 长度至少为 16
  * @throws DecryptionFailedException 解密错误时
  */
-fun ByteArray.decryptBy(key: IoBuffer, length: Int = this.size): ByteArray {
+@PublishedApi
+internal fun ByteArray.decryptBy(key: IoBuffer, length: Int = this.size): ByteArray {
     checkDataLengthAndReturnSelf(length)
     return ByteArrayPool.useInstance { keyBuffer ->
         key.readFully(keyBuffer, 0, key.readRemaining)
@@ -88,7 +95,8 @@ fun ByteArray.decryptBy(key: IoBuffer, length: Int = this.size): ByteArray {
  * @param keyHex 长度至少为 16 bytes
  * @throws DecryptionFailedException 解密错误时
  */
-fun ByteArray.decryptBy(keyHex: String, length: Int = this.size): ByteArray = decryptBy(keyHex.hexToBytes(withCache = true), length = length)
+@PublishedApi
+internal fun ByteArray.decryptBy(keyHex: String, length: Int = this.size): ByteArray = decryptBy(keyHex.hexToBytes(withCache = true), length = length)
 
 /**
  * 在 [ByteArrayPool] 缓存 [this], 然后使用 [key] 解密.
@@ -96,7 +104,8 @@ fun ByteArray.decryptBy(keyHex: String, length: Int = this.size): ByteArray = de
  * @param key 长度至少为 16
  * @throws DecryptionFailedException 解密错误时
  */
-fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = readRemaining - offset): ByteArray {
+@PublishedApi
+internal fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = readRemaining - offset): ByteArray {
     return ByteArrayPool.useInstance {
         this.readFully(it, offset, length)
         it.checkDataLengthAndReturnSelf(length)
@@ -110,12 +119,45 @@ fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = readRemain
  * @param keyHex 长度至少为 16
  * @throws DecryptionFailedException 解密错误时
  */
-fun IoBuffer.decryptBy(keyHex: String, offset: Int = 0, length: Int = readRemaining - offset): ByteArray =
+@PublishedApi
+internal fun IoBuffer.decryptBy(keyHex: String, offset: Int = 0, length: Int = readRemaining - offset): ByteArray =
     decryptBy(keyHex.hexToBytes(withCache = true), offset = offset, length = length)
 
 
 // endregion
 
+// region ByteReadPacket extension
+
+@PublishedApi
+internal fun ByteReadPacket.decryptBy(key: ByteArray): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) }
+
+@PublishedApi
+internal fun ByteReadPacket.decryptBy(key: IoBuffer): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) }
+
+@PublishedApi
+internal fun ByteReadPacket.decryptBy(keyHex: String): ByteReadPacket = decryptBy(keyHex.hexToBytes())
+
+@PublishedApi
+internal inline fun <R> ByteReadPacket.decryptAsByteArray(key: ByteArray, consumer: (ByteArray) -> R): R =
+    ByteArrayPool.useInstance {
+        val length = remaining.toInt()
+        readFully(it, 0, length)
+        consumer(it.decryptBy(key, length))
+    }.also { close() }
+
+@PublishedApi
+internal inline fun <R> ByteReadPacket.decryptAsByteArray(keyHex: String, consumer: (ByteArray) -> R): R =
+    this.decryptAsByteArray(keyHex.hexToBytes(), consumer)
+
+@PublishedApi
+internal inline fun <R> ByteReadPacket.decryptAsByteArray(key: IoBuffer, consumer: (ByteArray) -> R): R =
+    ByteArrayPool.useInstance {
+        val length = remaining.toInt()
+        readFully(it, 0, length)
+        consumer(it.decryptBy(key, length))
+    }.also { close() }
+
+// endregion
 private object TEA {
     private const val UINT32_MASK = 0xffffffffL
 
@@ -362,9 +404,4 @@ private object TEA {
 private fun ByteArray.checkDataLengthAndReturnSelf(length: Int = this.size): ByteArray {
     require(length % 8 == 0 && length >= 16) { "data must len % 8 == 0 && len >= 16 but given (length=$length) ${this.toUHexString()}" }
     return this
-}
-
-
-fun main() {
-    println("A4 F1 91 88 C9 82 14 99 0C 9E 56 55 91 23 C8 3D".hexToBytes().encryptBy("A4 F1 91 88 C9 82 14 99 0C 9E 56 55 91 23 C8 3D").decryptBy("A4 F1 91 88 C9 82 14 99 0C 9E 56 55 91 23 C8 3D").toUHexString() == "A4 F1 91 88 C9 82 14 99 0C 9E 56 55 91 23 C8 3D")
 }

+ 2 - 2
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformSocket.kt

@@ -23,9 +23,9 @@ expect class ClosedChannelException : IOException
 /**
  * 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误.
  */
-class SendPacketInternalException(cause: Throwable?) : Exception(cause)
+internal class SendPacketInternalException(cause: Throwable?) : Exception(cause)
 
 /**
  * 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误.
  */
-class ReadPacketInternalException(cause: Throwable?) : Exception(cause)
+internal class ReadPacketInternalException(cause: Throwable?) : Exception(cause)

+ 2 - 1
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/TypeConvertion.kt

@@ -92,7 +92,8 @@ fun String.hexToUBytes(withCache: Boolean = true): UByteArray =
 /**
  * 生成长度为 [length], 元素为随机 `0..255` 的 [ByteArray]
  */
-fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0..255).toByte() }
+@PublishedApi
+internal fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0..255).toByte() }
 
 /**
  * 随机生成长度为 [length] 的 [String].

+ 1 - 1
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/event/SubscribersJvm.kt

@@ -9,6 +9,6 @@ import kotlinx.coroutines.runBlocking
  */
 object Events {
     @JvmStatic
-    fun <E : Event> subscribe(type: Class<E>, handler: suspend (E) -> ListeningStatus) =
+    fun <E : Subscribable> subscribe(type: Class<E>, handler: suspend (E) -> ListeningStatus) =
         runBlocking { type.kotlin.subscribe(handler) }
 }

+ 1 - 2
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/network/protocol/tim/NetworkDispatcherJvm.kt

@@ -7,5 +7,4 @@ import java.util.concurrent.Executors
 /**
  * 独立的 4 thread 调度器
  */
-actual val NetworkDispatcher: CoroutineDispatcher
-    get() = Executors.newFixedThreadPool(4).asCoroutineDispatcher()
+internal actual val NetworkDispatcher: CoroutineDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher()

+ 18 - 3
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/DefaultCaptchaSolverJvm.kt

@@ -1,22 +1,27 @@
 package net.mamoe.mirai.utils
 
-import io.ktor.util.KtorExperimentalAPI
-import io.ktor.util.cio.writeChannel
+import kotlinx.coroutines.CoroutineName
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.io.ByteWriteChannel
+import kotlinx.coroutines.io.jvm.nio.copyTo
+import kotlinx.coroutines.io.reader
 import kotlinx.coroutines.sync.Mutex
 import kotlinx.coroutines.sync.withLock
 import kotlinx.coroutines.withContext
+import kotlinx.io.core.use
 import java.awt.Image
 import java.awt.image.BufferedImage
 import java.io.File
+import java.io.RandomAccessFile
 import javax.imageio.ImageIO
+import kotlin.coroutines.CoroutineContext
 
 /**
  * 平台默认的验证码识别器.
  *
  * 可被修改, 除覆盖配置外全局生效.
  */
-@KtorExperimentalAPI
 actual var DefaultCaptchaSolver: CaptchaSolver = {
     captchaLock.withLock {
         val tempFile: File = createTempFile(suffix = ".png").apply { deleteOnExit() }
@@ -38,6 +43,16 @@ actual var DefaultCaptchaSolver: CaptchaSolver = {
     }
 }
 
+// Copied from Ktor CIO
+private fun File.writeChannel(
+    coroutineContext: CoroutineContext = Dispatchers.IO
+): ByteWriteChannel = GlobalScope.reader(CoroutineName("file-writer") + coroutineContext, autoFlush = true) {
+    RandomAccessFile(this@writeChannel, "rw").use { file ->
+        val copied = channel.copyTo(file.channel)
+        file.setLength(copied) // truncate tail that could remain from the previously written data
+    }
+}.channel
+
 
 private val captchaLock = Mutex()
 

+ 13 - 22
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/MiraiLoggerJvm.kt

@@ -1,7 +1,5 @@
 package net.mamoe.mirai.utils
 
-import java.io.ByteArrayOutputStream
-import java.io.PrintStream
 import java.text.SimpleDateFormat
 import java.util.*
 
@@ -11,42 +9,39 @@ actual typealias PlatformLogger = Console
  * JVM 控制台日志实现
  */
 open class Console @JvmOverloads internal constructor(
-    override val identity: String? = null
+    override val identity: String? = "Mirai"
 ) : MiraiLoggerPlatformBase() {
-    override fun verbose0(any: Any?) = println(any.toString(), LoggerTextFormat.RESET)
+    override fun verbose0(any: Any?) = println(any, LoggerTextFormat.RESET)
     override fun verbose0(message: String?, e: Throwable?) {
-        verbose(message.toString())
+        if (message != null) verbose(message.toString())
         e?.printStackTrace()
     }
 
-    override fun info0(any: Any?) = println(any.toString(), LoggerTextFormat.LIGHT_GREEN)
+    override fun info0(any: Any?) = println(any, LoggerTextFormat.LIGHT_GREEN)
     override fun info0(message: String?, e: Throwable?) {
-        info(message.toString())
+        if (message != null) info(message.toString())
         e?.printStackTrace()
     }
 
-    override fun warning0(any: Any?) = println(any.toString(), LoggerTextFormat.LIGHT_RED)
+    override fun warning0(any: Any?) = println(any, LoggerTextFormat.LIGHT_RED)
     override fun warning0(message: String?, e: Throwable?) {
-        warning(message.toString())
+        if (message != null) warning(message.toString())
         e?.printStackTrace()
     }
 
-    override fun error0(any: Any?) = println(any.toString(), LoggerTextFormat.RED)
+    override fun error0(any: Any?) = println(any, LoggerTextFormat.RED)
     override fun error0(message: String?, e: Throwable?) {
-        error(message.toString())
+        if (message != null) error(message.toString())
         e?.printStackTrace()
     }
 
-    override fun debug0(any: Any?) {
-        println(any.toString(), LoggerTextFormat.LIGHT_CYAN)
-    }
-
+    override fun debug0(any: Any?) = println(any, LoggerTextFormat.LIGHT_CYAN)
     override fun debug0(message: String?, e: Throwable?) {
-        debug(message.toString())
+        if (message != null) debug(message.toString())
         e?.printStackTrace()
     }
 
-    private fun println(value: String?, color: LoggerTextFormat) {
+    private fun println(value: Any?, color: LoggerTextFormat) {
         val time = SimpleDateFormat("HH:mm:ss", Locale.SIMPLIFIED_CHINESE).format(Date())
 
         if (identity == null) {
@@ -81,8 +76,4 @@ internal enum class LoggerTextFormat(private val format: String) {
     ;
 
     override fun toString(): String = format
-}
-
-@Suppress("unused")
-val Throwable.stacktraceString: String
-    get() = ByteArrayOutputStream().also { printStackTrace(PrintStream(it)) }.toString()
+}

+ 7 - 6
mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt

@@ -8,10 +8,11 @@ import net.mamoe.mirai.Bot
 import net.mamoe.mirai.BotAccount
 import net.mamoe.mirai.contact.QQ
 import net.mamoe.mirai.event.*
-import net.mamoe.mirai.event.events.FriendMessageEvent
 import net.mamoe.mirai.login
 import net.mamoe.mirai.message.*
 import net.mamoe.mirai.network.protocol.tim.packet.action.uploadImage
+import net.mamoe.mirai.network.protocol.tim.packet.event.FriendMessage
+import net.mamoe.mirai.network.protocol.tim.packet.event.GroupMessage
 import net.mamoe.mirai.network.protocol.tim.packet.login.ifFail
 import net.mamoe.mirai.utils.suspendToExternalImage
 import java.io.File
@@ -52,7 +53,7 @@ suspend fun main() {
     directlySubscribe(bot)
 
     //DSL 监听
-    subscribeAll<FriendMessageEvent> {
+    subscribeAll<FriendMessage> {
         always {
             //获取第一个纯文本消息
             val firstText = it.message.firstOrNull<PlainText>()
@@ -102,7 +103,7 @@ suspend fun Bot.messageDSL() {
             // sender: QQ
             // it: String (MessageChain.toString)
 
-            if (this is GroupSenderAndMessage) {
+            if (this is GroupMessage) {
                 //如果是群消息
                 // group: Group
                 this.group.sendMessage("你在一个群里")
@@ -196,7 +197,7 @@ suspend fun directlySubscribe(bot: Bot) {
     // 手动处理消息
     // 使用 Bot 的扩展方法监听, 将在处理事件时得到一个 this: Bot.
     // 这样可以调用 Bot 内的一些扩展方法如 UInt.qq():QQ
-    bot.subscribeAlways<FriendMessageEvent> {
+    bot.subscribeAlways<FriendMessage> {
         // this: Bot
         // it: FriendMessageEvent
 
@@ -257,9 +258,9 @@ suspend fun directlySubscribe(bot: Bot) {
  * 对机器人说 "停止", 机器人停止
  */
 suspend fun demo2() {
-    subscribeAlways<FriendMessageEvent> { event ->
+    subscribeAlways<FriendMessage> { event ->
         if (event.message eq "记笔记") {
-            subscribeUntilFalse<FriendMessageEvent> {
+            subscribeUntilFalse<FriendMessage> {
                 it.reply("你发送了 ${it.message}")
 
                 it.message eq "停止"

+ 1 - 0
mirai-demos/mirai-demo-gentleman/build.gradle

@@ -4,6 +4,7 @@ apply plugin: "java"
 dependencies {
     api project(":mirai-core")
     runtime files("../../mirai-core/build/classes/kotlin/jvm/main") // mpp targeting android limitation
+    //runtime files("../../mirai-core/build/classes/atomicfu/jvm/main") // mpp targeting android limitation
     api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlin_version
     api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutines_version
 

+ 2 - 2
mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Main.kt

@@ -7,7 +7,7 @@ import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import net.mamoe.mirai.Bot
 import net.mamoe.mirai.BotAccount
-import net.mamoe.mirai.event.Event
+import net.mamoe.mirai.event.Subscribable
 import net.mamoe.mirai.event.subscribeAlways
 import net.mamoe.mirai.event.subscribeMessages
 import net.mamoe.mirai.login
@@ -41,7 +41,7 @@ suspend fun main() {
     /**
      * 监听所有事件
      */
-    subscribeAlways<Event> {
+    subscribeAlways<Subscribable> {
         //bot.logger.verbose("收到了一个事件: ${it::class.simpleName}")
     }
     bot.subscribeMessages {