Him188 6 年 前
コミット
2f67f8363b
100 ファイル変更2094 行追加1518 行削除
  1. 4 3
      README.md
  2. 1 1
      mirai-api-http/build.gradle.kts
  3. 1 3
      mirai-api-http/src/main/kotlin/net.mamoe.mirai.api.http/MiraiHttpApplication.kt
  4. 110 0
      mirai-core-timpc/build.gradle.kts
  5. 18 0
      mirai-core-timpc/src/androidMain/kotlin/net/mamoe/mirai/timpc/TIMPCBot.kt
  6. 3 2
      mirai-core-timpc/src/androidMain/kotlin/net/mamoe/mirai/timpc/network/NetworkDispatcher.kt
  7. 76 0
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/TIMPC.kt
  8. 243 0
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/TIMPCBot.kt
  9. 37 0
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/internal/RawGroupInfo.kt
  10. 83 46
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/ContactImpl.kt
  11. 61 88
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/TIMBotNetworkHandler.kt
  12. 1 1
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/TIMProtocol.kt
  13. 1 6
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/handler/DataPacketSocketAdapter.kt
  14. 37 0
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/handler/TemporaryPacketHandler.kt
  15. 1 1
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/Annotations.kt
  16. 11 2
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/Decrypters.kt
  17. 6 12
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/OutgoingPacket.kt
  18. 2 6
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/Packet.kt
  19. 6 5
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/PacketFactory.kt
  20. 5 5
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/PacketId.kt
  21. 25 36
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/AddContact.kt
  22. 17 51
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/FriendImage.kt
  23. 5 5
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/FriendList.kt
  24. 6 6
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/GradeInfo.kt
  25. 10 51
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/GroupImage.kt
  26. 35 79
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/GroupPacket.kt
  27. 3 3
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/HttpAPIAccessor.kt
  28. 3 23
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/Image.kt
  29. 21 20
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/Profile.kt
  30. 6 10
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/Remark.kt
  31. 7 6
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/RequestFriendListPacket.kt
  32. 10 12
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/SendFriendMessagePacket.kt
  33. 3 3
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/AndroidOnlineStatusChange.kt
  34. 2 8
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/ConnectionOccupiedEvent.kt
  35. 13 17
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/EventPacketFactory.kt
  36. 6 31
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/FriendAddRequestEventPacket.kt
  37. 6 4
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/FriendConversationIniliaze.kt
  38. 8 12
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/FriendOnlineStatusChanged.kt
  39. 3 2
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/GroupFileUpload.kt
  40. 2 1
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/Ignored.kt
  41. 11 10
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/MemberJoin.kt
  42. 9 13
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/MemberKickEvent.kt
  43. 6 73
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/MemberMute.kt
  44. 7 7
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/MemberPermission.kt
  45. 103 0
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/MessageEvent.kt
  46. 4 3
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/Unknown.kt
  47. 8 7
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/Captcha.kt
  48. 7 7
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/ChangeOnlineStatusPacket.kt
  49. 6 6
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/Heartbeat.kt
  50. 34 12
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/PasswordSubmission.kt
  51. 9 13
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/SKey.kt
  52. 6 5
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/Session.kt
  53. 6 5
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/Touch.kt
  54. 7 0
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/utils/Unreachable.kt
  55. 11 0
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/utils/unsupportedFlag.kt
  56. 30 0
      mirai-core-timpc/src/jvmMain/kotlin/net/mamoe/mirai/timpc/TIMPCBot.kt
  57. 13 0
      mirai-core-timpc/src/jvmMain/kotlin/net/mamoe/mirai/timpc/network/NetworkDispatcher.kt
  58. 4 6
      mirai-core-timpc/src/jvmTest/kotlin/BadQQFilter.kt
  59. 21 38
      mirai-core-timpc/src/jvmTest/kotlin/PacketDebugger.kt
  60. 3 4
      mirai-core-timpc/src/jvmTest/kotlin/packetdebugger/PacketDecoder.kt
  61. 3 0
      mirai-core-timpc/src/main/AndroidManifest.xml
  62. 3 2
      mirai-core/src/androidMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt
  63. 0 30
      mirai-core/src/androidMain/kotlin/net/mamoe/mirai/network/BotSession.kt
  64. 0 2
      mirai-core/src/androidMain/kotlin/net/mamoe/mirai/network/PlatformBotSession.kt
  65. 1 3
      mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsAndroid.kt
  66. 0 0
      mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/WeakRefAndroid.kt
  67. 8 0
      mirai-core/src/androidTest/java/ECDHTest.kt
  68. 224 0
      mirai-core/src/androidTest/java/EcdhCrypt.java
  69. 70 56
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt
  70. 2 4
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAccount.kt
  71. 0 122
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAddFriend.kt
  72. 70 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotFactory.kt
  73. 5 61
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotHelper.kt
  74. 29 120
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt
  75. 12 15
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt
  76. 7 7
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt
  77. 7 18
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt
  78. 11 11
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/GroupIdConvertions.kt
  79. 3 6
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt
  80. 8 21
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt
  81. 22 24
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/SubscribersWithBot.kt
  82. 10 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/ConnectionOccupiedEvent.kt
  83. 10 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/FriendStatusChanged.kt
  84. 75 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/MuteEvent.kt
  85. 0 48
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/PacketEvents.kt
  86. 27 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/ReceiveFriendAddRequestEvent.kt
  87. 30 104
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt
  88. 0 11
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Face.kt
  89. 26 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/FriendMessage.kt
  90. 35 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt
  91. 91 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt
  92. 2 2
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/At.kt
  93. 13 4
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Face.kt
  94. 7 5
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt
  95. 1 1
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt
  96. 42 43
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt
  97. 1 1
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt
  98. 5 3
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/XML.kt
  99. 8 7
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/internal/MessageDataInternal.kt
  100. 23 17
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt

+ 4 - 3
README.md

@@ -31,17 +31,18 @@ repositories{
 您需要将 `VERSION` 替换为最新的版本(如 `0.5.1`): [![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/)  
 Mirai 目前还处于实验性阶段, 建议您时刻保持最新版本.
 
+现在 Mirai 只支持 TIM PC 协议.
 **common**
 ```kotlin
-implementation("net.mamoe:mirai-core-common:VERSION")
+implementation("net.mamoe:mirai-core-timpc-common:VERSION")
 ```
 **jvm**
 ```kotlin
-implementation("net.mamoe:mirai-core-jvm:VERSION")
+implementation("net.mamoe:mirai-core-timpc-jvm:VERSION")
 ```
 **android**
 ```kotlin
-implementation("net.mamoe:mirai-core-android:VERSION")
+implementation("net.mamoe:mirai-core-timpc-android:VERSION")
 ```
 
 ## Try

+ 1 - 1
mirai-api-http/build.gradle.kts

@@ -33,7 +33,7 @@ kotlin {
 
     sourceSets["main"].apply {
         dependencies {
-            implementation(project(":mirai-core"))
+            implementation(project(":mirai-core-timpc"))
 
             implementation(kotlin("stdlib-jdk8", kotlinVersion))
             implementation(kotlin("stdlib-jdk7", kotlinVersion))

+ 1 - 3
mirai-api-http/src/main/kotlin/net.mamoe.mirai.api.http/MiraiHttpApplication.kt

@@ -17,9 +17,7 @@ import io.ktor.util.pipeline.ContextDsl
 import io.ktor.util.pipeline.PipelineContext
 import io.ktor.util.pipeline.PipelineInterceptor
 import net.mamoe.mirai.Bot
-import net.mamoe.mirai.addFriend
 import net.mamoe.mirai.contact.sendMessage
-import net.mamoe.mirai.getGroup
 import net.mamoe.mirai.utils.io.hexToBytes
 import net.mamoe.mirai.utils.io.hexToUBytes
 
@@ -40,7 +38,7 @@ fun Application.mirai() {
         }
 
         mirai("/sendGroupMessage") {
-            Bot.instanceWhose(qq = param("bot")).getGroup(param<UInt>("group")).sendMessage(param<String>("message"))
+            Bot.instanceWhose(qq = param("bot")).getGroup(param<Long>("group")).sendMessage(param<String>("message"))
             call.ok()
         }
 

+ 110 - 0
mirai-core-timpc/build.gradle.kts

@@ -0,0 +1,110 @@
+@file:Suppress("UNUSED_VARIABLE")
+
+plugins {
+    kotlin("multiplatform")
+    id("kotlinx-atomicfu")
+    id("com.android.library")
+    id("kotlinx-serialization")
+    `maven-publish`
+    id("com.jfrog.bintray") version "1.8.4-jetbrains-3" // DO NOT CHANGE THIS VERSION UNLESS YOU WANT TO WASTE YOUR TIME
+}
+
+apply(from = rootProject.file("gradle/publish.gradle"))
+
+val kotlinVersion: String by rootProject.ext
+val atomicFuVersion: String by rootProject.ext
+val coroutinesVersion: String by rootProject.ext
+val kotlinXIoVersion: String by rootProject.ext
+val coroutinesIoVersion: String by rootProject.ext
+
+val klockVersion: String by rootProject.ext
+val ktorVersion: String by rootProject.ext
+
+val serializationVersion: String by rootProject.ext
+
+fun kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version"
+
+fun ktor(id: String, version: String) = "io.ktor:ktor-$id:$version"
+
+
+description = "QQ protocol library"
+
+kotlin {
+    android("android") {
+        publishAllLibraryVariants()
+        project.android {
+            compileSdkVersion(29)
+
+            defaultConfig {
+                minSdkVersion(15)
+            }
+
+            // sourceSets.filterIsInstance(com.android.build.gradle.api.AndroidSourceSet::class.java).forEach {
+            //     it.manifest.srcFile("src/androidMain/res/AndroidManifest.xml")
+            //     it.res.srcDirs(file("src/androidMain/res"))
+            // }
+            //(sourceSets["main"] as AndroidSourceSet).java.srcDirs(file("src/androidMain/kotlin"))
+        }
+    }
+
+    jvm("jvm") {
+    }
+
+    sourceSets {
+        all {
+            languageSettings.enableLanguageFeature("InlineClasses")
+            languageSettings.useExperimentalAnnotation("kotlin.Experimental")
+
+            dependencies {
+                api(project(":mirai-core"))
+
+                api(kotlin("stdlib", kotlinVersion))
+                api(kotlin("serialization", kotlinVersion))
+
+                api("org.jetbrains.kotlinx:atomicfu:$atomicFuVersion")
+                api(kotlinx("io", kotlinXIoVersion))
+                api(kotlinx("coroutines-io", coroutinesIoVersion))
+                api(kotlinx("coroutines-core", coroutinesVersion))
+            }
+        }
+        commonMain {
+            dependencies {
+            }
+        }
+        commonTest {
+            dependencies {
+                api(kotlin("test-annotations-common"))
+                api(kotlin("test-common"))
+            }
+        }
+
+        val androidMain by getting {
+            dependencies {
+            }
+        }
+
+        val androidTest by getting {
+            dependencies {
+                api(kotlin("test", kotlinVersion))
+                api(kotlin("test-junit", kotlinVersion))
+                api(kotlin("test-annotations-common"))
+                api(kotlin("test-common"))
+            }
+        }
+
+        val jvmMain by getting {
+            dependencies {
+            }
+        }
+
+        val jvmTest by getting {
+            dependencies {
+                api(kotlin("test", kotlinVersion))
+                api(kotlin("test-junit", kotlinVersion))
+                implementation("org.pcap4j:pcap4j-distribution:1.8.2")
+
+                //runtimeOnly(files("build/classes/kotlin/jvm/test")) // classpath is not properly set by IDE
+            }
+        }
+    }
+}

+ 18 - 0
mirai-core-timpc/src/androidMain/kotlin/net/mamoe/mirai/timpc/TIMPCBot.kt

@@ -0,0 +1,18 @@
+@file:Suppress("unused")
+
+package net.mamoe.mirai.timpc
+
+import kotlinx.io.InputStream
+import kotlinx.io.streams.inputStream
+import net.mamoe.mirai.BotAccount
+import net.mamoe.mirai.message.data.Image
+import net.mamoe.mirai.utils.MiraiLogger
+import kotlin.coroutines.CoroutineContext
+
+internal actual class TIMPCBot actual constructor(
+    account: BotAccount,
+    logger: MiraiLogger?,
+    context: CoroutineContext
+) : TIMPCBotBase(account, logger, context) {
+    suspend inline fun Image.downloadAsStream(): InputStream = download().inputStream()
+}

+ 3 - 2
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/network/protocol/timpc/NetworkDispatcherAndroid.kt → mirai-core-timpc/src/androidMain/kotlin/net/mamoe/mirai/timpc/network/NetworkDispatcher.kt

@@ -1,4 +1,4 @@
-package net.mamoe.mirai.network.protocol.timpc
+package net.mamoe.mirai.timpc.network
 
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.asCoroutineDispatcher
@@ -9,4 +9,5 @@ import java.util.concurrent.Executors
  *
  * JVM: 独立的 4 thread 调度器
  */
-internal actual val NetworkDispatcher: CoroutineDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher()
+internal actual val NetworkDispatcher: CoroutineDispatcher
+    get() = Executors.newFixedThreadPool(4).asCoroutineDispatcher()

+ 76 - 0
mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/TIMPC.kt

@@ -0,0 +1,76 @@
+@file:Suppress("FunctionName", "unused", "SpellCheckingInspection")
+
+package net.mamoe.mirai.timpc
+
+import kotlinx.coroutines.CoroutineScope
+import net.mamoe.mirai.Bot
+import net.mamoe.mirai.BotAccount
+import net.mamoe.mirai.BotFactory
+import net.mamoe.mirai.timpc.TIMPC.Bot
+import net.mamoe.mirai.utils.MiraiLogger
+import kotlin.coroutines.coroutineContext
+
+/**
+ * TIM PC 协议的 [Bot] 构造器.
+ */
+object TIMPC : BotFactory {
+    /**
+     * 在当前 CoroutineScope 下构造 Bot 实例
+     * 该 Bot 实例的生命周期将会跟随这个 CoroutineScope.
+     * 这个 CoroutineScope 也会等待 Bot 的结束才会结束.
+     *
+     * ```kotlin
+     * suspend fun myProcess(){
+     *   TIMPC.Bot(account, logger)
+     * }
+     * ```
+     */
+    override suspend fun Bot(account: BotAccount, logger: MiraiLogger?): Bot =
+        TIMPCBot(account, logger, coroutineContext)
+
+    /**
+     * 在当前 CoroutineScope 下构造 Bot 实例
+     * 该 Bot 实例的生命周期将会跟随这个 CoroutineScope.
+     * 这个 CoroutineScope 也会等待 Bot 的结束才会结束.
+     *
+     * ```kotlin
+     * suspend fun myProcess(){
+     *   TIMPC.Bot(account, logger)
+     * }
+     * ```
+     */
+    override suspend fun Bot(qq: Long, password: String, logger: MiraiLogger?): Bot =
+        TIMPCBot(BotAccount(qq, password), logger, coroutineContext)
+
+    /**
+     * 在特定的 CoroutineScope 下构造 Bot 实例
+     * 该 Bot 实例的生命周期将会跟随这个 CoroutineScope.
+     * 这个 CoroutineScope 也会等待 Bot 的结束才会结束.
+     *
+     * ```kotlin
+     * fun myProcess(){
+     *   TIMPC.run {
+     *     GlobalScope.Bot(account, logger)
+     *   }
+     * }
+     * ```
+     */
+    override fun CoroutineScope.Bot(qq: Long, password: String, logger: MiraiLogger?): Bot =
+        TIMPCBot(BotAccount(qq, password), logger, coroutineContext)
+
+    /**
+     * 在特定的 CoroutineScope 下构造 Bot 实例
+     * 该 Bot 实例的生命周期将会跟随这个 CoroutineScope.
+     * 这个 CoroutineScope 也会等待 Bot 的结束才会结束.
+     *
+     * ```kotlin
+     * fun myProcess(){
+     *   TIMPC.run {
+     *     GlobalScope.Bot(account, logger)
+     *   }
+     * }
+     * ```
+     */
+    override fun CoroutineScope.Bot(account: BotAccount, logger: MiraiLogger?): Bot =
+        TIMPCBot(account, logger, coroutineContext)
+}

+ 243 - 0
mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/TIMPCBot.kt

@@ -0,0 +1,243 @@
+package net.mamoe.mirai.timpc
+
+import kotlinx.coroutines.*
+import net.mamoe.mirai.Bot
+import net.mamoe.mirai.BotAccount
+import net.mamoe.mirai.BotImpl
+import net.mamoe.mirai.contact.*
+import net.mamoe.mirai.message.data.Image
+import net.mamoe.mirai.message.data.ImageId0x03
+import net.mamoe.mirai.message.data.ImageId0x06
+import net.mamoe.mirai.network.data.*
+import net.mamoe.mirai.qqAccount
+import net.mamoe.mirai.timpc.internal.RawGroupInfo
+import net.mamoe.mirai.timpc.network.GroupImpl
+import net.mamoe.mirai.timpc.network.MemberImpl
+import net.mamoe.mirai.timpc.network.QQImpl
+import net.mamoe.mirai.timpc.network.TIMBotNetworkHandler
+import net.mamoe.mirai.timpc.network.handler.TemporaryPacketHandler
+import net.mamoe.mirai.timpc.network.packet.KnownPacketId
+import net.mamoe.mirai.timpc.network.packet.OutgoingPacket
+import net.mamoe.mirai.timpc.network.packet.SessionKey
+import net.mamoe.mirai.timpc.network.packet.action.*
+import net.mamoe.mirai.timpc.utils.assertUnreachable
+import net.mamoe.mirai.utils.*
+import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
+import net.mamoe.mirai.utils.io.logStacktrace
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
+import kotlin.coroutines.CoroutineContext
+import kotlin.jvm.JvmSynthetic
+
+internal expect class TIMPCBot constructor(
+    account: BotAccount,
+    logger: MiraiLogger?,
+    context: CoroutineContext
+) : TIMPCBotBase
+
+@UseExperimental(MiraiInternalAPI::class)
+internal abstract class TIMPCBotBase constructor(
+    account: BotAccount,
+    logger: MiraiLogger?,
+    context: CoroutineContext
+) : BotImpl<TIMBotNetworkHandler>(account, logger ?: DefaultLogger("Bot(" + account.id + ")"), context) {
+    companion object {
+        init {
+            KnownPacketId.values() /* load id classes */
+        }
+    }
+
+    final override val network: TIMBotNetworkHandler get() = _network
+
+    inline val sessionKey: SessionKey get() = network.sessionKey
+
+    private lateinit var _network: TIMBotNetworkHandler
+
+    override suspend fun login(configuration: BotConfiguration) =
+        reinitializeNetworkHandler(configuration, null)
+
+    // shouldn't be suspend!! This function MUST NOT inherit the context from the caller because the caller(NetworkHandler) is going to close
+    internal fun tryReinitializeNetworkHandler(
+        configuration: BotConfiguration,
+        cause: Throwable?
+    ): Job = launch {
+        repeat(configuration.reconnectionRetryTimes) {
+            try {
+                reinitializeNetworkHandler(configuration, cause)
+                logger.info("Reconnected successfully")
+                return@launch
+            } catch (e: LoginFailedException){
+                delay(configuration.reconnectPeriodMillis)
+            }
+        }
+    }
+
+    private suspend fun reinitializeNetworkHandler(
+        configuration: BotConfiguration,
+        cause: Throwable?
+    ) {
+        logger.info("BotAccount: $qqAccount")
+        logger.info("Initializing BotNetworkHandler")
+        try {
+            if (::_network.isInitialized) {
+                _network.close(cause)
+            }
+        } catch (e: Exception) {
+            logger.error("Cannot close network handler", e)
+        }
+        _network = TIMBotNetworkHandler(this.coroutineContext + configuration, this as TIMPCBot)
+
+        _network.login()
+    }
+
+    final override suspend fun addFriend(id: Long, message: String?, remark: String?): AddFriendResult {
+        return when (CanAddFriendPacket(qqAccount, id, sessionKey).sendAndExpect<CanAddFriendResponse>()) {
+            is CanAddFriendResponse.AlreadyAdded -> AddFriendResult.ALREADY_ADDED
+            is CanAddFriendResponse.Rejected -> AddFriendResult.REJECTED
+
+            is CanAddFriendResponse.ReadyToAdd,
+            is CanAddFriendResponse.RequireVerification -> {
+                val key = RequestFriendAdditionKeyPacket(qqAccount, id, sessionKey).sendAndExpect<RequestFriendAdditionKeyPacket.Response>().key
+                AddFriendPacket.RequestAdd(qqAccount, id, sessionKey, message, remark, key).sendAndExpect<AddFriendPacket.Response>()
+                AddFriendResult.WAITING_FOR_APPROVE
+            } //这个做的是需要验证消息的情况, 不确定 ReadyToAdd 的是啥
+
+            // 似乎 RequireVerification 和 ReadyToAdd 判断错了. 需要重新检查一下
+
+            // TODO: 2019/11/11 需要验证问题的情况
+
+            /*is CanAddFriendResponse.ReadyToAdd -> {
+            // TODO: 2019/11/11 这不需要验证信息的情况
+
+            //AddFriendPacket(qqAccount, id, sessionKey, ).sendAndExpectAsync<AddFriendPacket.Response>().await()
+            TODO()
+        }*/
+        }
+    }
+
+    final override suspend fun approveFriendAddRequest(id: Long, remark: String?) {
+        AddFriendPacket.Approve(qqAccount, sessionKey, 0, id, remark).sendAndExpect<AddFriendPacket.Response>()
+    }
+
+
+    // region contacts
+
+    final override val groups: ContactList<Group> = ContactList(LockFreeLinkedList())
+    final override val qqs: ContactList<QQ> = ContactList(LockFreeLinkedList())
+
+    /**
+     * 线程安全地获取缓存的 QQ 对象. 若没有对应的缓存, 则会创建一个.
+     */
+    @UseExperimental(MiraiInternalAPI::class)
+    @JvmSynthetic
+    final override fun getQQ(id: Long): QQ = qqs.delegate.getOrAdd(id) { QQ(id) }
+
+    @UseExperimental(MiraiInternalAPI::class, ExperimentalUnsignedTypes::class)
+    final override suspend fun getGroup(id: GroupId): Group = groups.delegate.getOrNull(id.value) ?: inline {
+        val info: RawGroupInfo = try {
+            when (val response =
+                GroupPacket.QueryGroupInfo(qqAccount, id.toInternalId(), sessionKey).sendAndExpect<GroupPacket.InfoResponse>()) {
+                is RawGroupInfo -> response
+                is GroupNotFound -> throw GroupNotFoundException("id=${id.value}")
+                else -> assertUnreachable()
+            }
+        } catch (e: Exception) {
+            throw IllegalStateException("Cannot obtain group info for id ${id.value}", e)
+        }
+
+        return groups.delegate.getOrAdd(id.value) { Group(id, info) }
+    }
+
+    final override suspend fun getGroup(internalId: GroupInternalId): Group =
+        getGroup0(internalId.toId().value)
+
+    private suspend inline fun getGroup0(id: Long): Group =
+        groups.delegate.getOrNull(id) ?: inline {
+            val info: RawGroupInfo = try {
+                GroupPacket.QueryGroupInfo(qqAccount, GroupId(id).toInternalId(), sessionKey).sendAndExpect()
+            } catch (e: Exception) {
+                e.logStacktrace()
+                error("Cannot obtain group info for id $id")
+            }
+
+            return groups.delegate.getOrAdd(id) { Group(GroupId(id), info) }
+        }
+
+    @UseExperimental(MiraiInternalAPI::class)
+    final override suspend fun getGroup(id: Long): Group = getGroup0(id.coerceAtLeastOrFail(0))
+
+
+    internal suspend inline fun <reified P : Packet, R> OutgoingPacket.sendAndExpectAsync(
+        checkSequence: Boolean = true,
+        noinline handler: suspend (P) -> R
+    ): Deferred<R> {
+        val deferred: CompletableDeferred<R> = CompletableDeferred(coroutineContext[Job])
+        network.temporaryPacketHandlers.addLast(
+            TemporaryPacketHandler(
+                expectationClass = P::class,
+                deferred = deferred,
+                checkSequence = if (checkSequence) this.sequenceId else null,
+                callerContext = coroutineContext + deferred,
+                handler = handler
+            )
+        )
+        network.socket.sendPacket(this)
+        return deferred
+    }
+
+    internal suspend inline fun <reified P : Packet> OutgoingPacket.sendAndExpectAsync(checkSequence: Boolean = true): Deferred<P> =
+        sendAndExpectAsync<P, P>(checkSequence) { it }
+
+    internal suspend inline fun <reified P : Packet, R> OutgoingPacket.sendAndExpect(
+        checkSequence: Boolean = true,
+        timeoutMillis: Long = 5.secondsToMillis,
+        crossinline mapper: (P) -> R
+    ): R = withTimeout(timeoutMillis) { sendAndExpectAsync<P, R>(checkSequence) { mapper(it) }.await() }
+
+    internal suspend inline fun <reified P : Packet> OutgoingPacket.sendAndExpect(
+        checkSequence: Boolean = true,
+        timeoutMillist: Long = 5.secondsToMillis
+    ): P = withTimeout(timeoutMillist) { sendAndExpectAsync<P, P>(checkSequence) { it }.await() }
+
+    internal suspend inline fun OutgoingPacket.send() = network.socket.sendPacket(this)
+
+
+    final override suspend fun Image.getLink(): ImageLink = when (val id = this.id) {
+        is ImageId0x03 -> GroupImagePacket.RequestImageLink(qqAccount, sessionKey, id).sendAndExpect<GroupImageLink>().requireSuccess()
+        is ImageId0x06 -> FriendImagePacket.RequestImageLink(qqAccount, sessionKey, id).sendAndExpect<FriendImageLink>()
+        else -> assertUnreachable()
+    }
+
+    @Suppress("FunctionName")
+    @PublishedApi
+    internal fun CoroutineScope.Group(groupId: GroupId, info: RawGroupInfo): Group {
+        return GroupImpl(this@TIMPCBotBase as TIMPCBot, groupId, coroutineContext).apply { this.info = info.parseBy(this); launch { startUpdater() } }
+    }
+
+    @Suppress("FunctionName")
+    @PublishedApi
+    internal fun Group.Member(qq: QQ, permission: MemberPermission): Member {
+        return MemberImpl(qq, this, permission, coroutineContext).apply { launch { startUpdater() } }
+    }
+
+    @Suppress("FunctionName")
+    internal fun CoroutineScope.QQ(id: Long): QQ =
+        QQImpl(this@TIMPCBotBase as TIMPCBot, id, coroutineContext).apply { launch { startUpdater() } }
+}
+
+internal inline fun <R> inline(block: () -> R): R = block()
+
+internal suspend fun TIMPCBot.sendPacket(toSend: OutgoingPacket) = this.network.socket.sendPacket(toSend)
+
+/**
+ * 以 [Bot] 作为接收器 (receiver) 并调用 [block], 返回 [block] 的返回值.
+ * 这个方法将能帮助使用在 [Bot] 中定义的一些扩展方法
+ */
+@UseExperimental(ExperimentalContracts::class)
+internal inline fun <R> Contact.withTIMPCBot(block: TIMPCBot.() -> R): R {
+    contract {
+        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
+    }
+    return (bot as TIMPCBot).run(block)
+}

+ 37 - 0
mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/internal/RawGroupInfo.kt

@@ -0,0 +1,37 @@
+package net.mamoe.mirai.timpc.internal
+
+import net.mamoe.mirai.contact.*
+import net.mamoe.mirai.network.data.GroupInfo
+import net.mamoe.mirai.timpc.TIMPCBot
+import net.mamoe.mirai.timpc.network.packet.action.GroupPacket
+import net.mamoe.mirai.utils.LockFreeLinkedList
+import net.mamoe.mirai.utils.MiraiInternalAPI
+
+data class RawGroupInfo(
+    val group: Long,
+    val owner: Long,
+    val name: String,
+    val announcement: String,
+    /**
+     * 含群主
+     */
+    val members: Map<Long, MemberPermission>
+) : GroupPacket.InfoResponse {
+
+    @UseExperimental(MiraiInternalAPI::class)
+    fun parseBy(group: Group): GroupInfo = group.withBot {
+        this as? TIMPCBot ?: error("internal error: wrong Bot instance passed")
+
+        val memberList = LockFreeLinkedList<Member>()
+        members.forEach { entry: Map.Entry<Long, MemberPermission> ->
+            memberList.addLast(entry.key.qq().let { group.Member(it, entry.value) })
+        }
+        return GroupInfo(
+            group,
+            [email protected]().let { group.Member(it, MemberPermission.OWNER) },
+            [email protected],
+            [email protected],
+            ContactList(memberList)
+        )
+    }
+}

+ 83 - 46
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/internal/ContactImpl.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/ContactImpl.kt

@@ -1,24 +1,29 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "MemberVisibilityCanBePrivate", "EXPERIMENTAL_UNSIGNED_LITERALS")
 
-package net.mamoe.mirai.contact.internal
+package net.mamoe.mirai.timpc.network
 
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
-import net.mamoe.mirai.Bot
+import kotlinx.coroutines.withContext
 import net.mamoe.mirai.contact.*
-import net.mamoe.mirai.contact.data.Profile
 import net.mamoe.mirai.event.subscribeAlways
-import net.mamoe.mirai.message.MessageChain
-import net.mamoe.mirai.network.protocol.timpc.packet.action.*
-import net.mamoe.mirai.network.protocol.timpc.packet.event.MemberJoinEventPacket
-import net.mamoe.mirai.network.protocol.timpc.packet.event.MemberQuitEvent
-import net.mamoe.mirai.network.qqAccount
-import net.mamoe.mirai.network.sessionKey
+import net.mamoe.mirai.message.data.ImageId
+import net.mamoe.mirai.message.data.MessageChain
+import net.mamoe.mirai.network.data.FriendNameRemark
+import net.mamoe.mirai.network.data.GroupInfo
+import net.mamoe.mirai.network.data.PreviousNameList
+import net.mamoe.mirai.network.data.Profile
 import net.mamoe.mirai.qqAccount
-import net.mamoe.mirai.sendPacket
-import net.mamoe.mirai.utils.MiraiInternalAPI
-import net.mamoe.mirai.withSession
+import net.mamoe.mirai.timpc.TIMPCBot
+import net.mamoe.mirai.timpc.internal.RawGroupInfo
+import net.mamoe.mirai.timpc.network.packet.action.*
+import net.mamoe.mirai.timpc.network.packet.event.MemberJoinEventPacket
+import net.mamoe.mirai.timpc.network.packet.event.MemberQuitEvent
+import net.mamoe.mirai.timpc.sendPacket
+import net.mamoe.mirai.timpc.utils.assertUnreachable
+import net.mamoe.mirai.timpc.withTIMPCBot
+import net.mamoe.mirai.utils.*
+import net.mamoe.mirai.utils.io.toUHexString
 import kotlin.coroutines.CoroutineContext
 
 internal sealed class ContactImpl : Contact {
@@ -30,22 +35,12 @@ internal sealed class ContactImpl : Contact {
     internal abstract suspend fun startUpdater()
 }
 
-/**
- * 构造 [Group]
- */
-@Suppress("FunctionName")
-@PublishedApi
-internal fun CoroutineScope.Group(bot: Bot, groupId: GroupId, info: RawGroupInfo, context: CoroutineContext): Group =
-    GroupImpl(bot, groupId, context).apply {
-        [email protected] = info.parseBy(this@apply)
-        launch { startUpdater() }
-    }
-
-
 @Suppress("MemberVisibilityCanBePrivate", "CanBeParameter")
-internal data class GroupImpl internal constructor(override val bot: Bot, val groupId: GroupId, override val coroutineContext: CoroutineContext) :
+internal class GroupImpl internal constructor(bot: TIMPCBot, val groupId: GroupId, override val coroutineContext: CoroutineContext) :
     ContactImpl(), Group, CoroutineScope {
-    override val id: UInt get() = groupId.value
+    override val bot: TIMPCBot by bot.unsafeWeakRef()
+
+    override val id: Long get() = groupId.value
     override val internalId = GroupId(id).toInternalId()
 
     internal lateinit var info: GroupInfo
@@ -56,19 +51,45 @@ internal data class GroupImpl internal constructor(override val bot: Bot, val gr
     override val announcement: String get() = info.announcement
     override val members: ContactList<Member> get() = info.members
 
-    override fun getMember(id: UInt): Member =
-        members.getOrNull(id) ?: throw NoSuchElementException("No such member whose id is ${id.toLong()} in group ${groupId.value.toLong()}")
+    override fun getMember(id: Long): Member =
+        members.getOrNull(id) ?: throw NoSuchElementException("No such member whose id is $id in group ${groupId.value}")
 
     override suspend fun sendMessage(message: MessageChain) {
         bot.sendPacket(GroupPacket.Message(bot.qqAccount, internalId, bot.sessionKey, message))
     }
 
-    override suspend fun updateGroupInfo(): GroupInfo = bot.withSession {
+    override suspend fun uploadImage(image: ExternalImage): ImageId = withTIMPCBot {
+        val userContext = coroutineContext
+        val response = GroupImagePacket.RequestImageId(bot.qqAccount, internalId, image, sessionKey).sendAndExpect<GroupImageResponse>()
+
+        withContext(userContext) {
+            when (response) {
+                is ImageUploadInfo -> response.uKey?.let {
+                    Http.postImage(
+                        htcmd = "0x6ff0071",
+                        uin = bot.qqAccount,
+                        groupId = GroupId(id),
+                        imageInput = image.input,
+                        inputSize = image.inputSize,
+                        uKeyHex = it.toUHexString("")
+                    )
+                } // if null: image already exists
+
+                // TODO: 2019/11/17 超过大小的情况
+                //is Overfile -> throw OverFileSizeMaxException()
+                else -> assertUnreachable()
+            }
+        }
+
+        return image.groupImageId
+    }
+
+    override suspend fun updateGroupInfo(): GroupInfo = withTIMPCBot {
         GroupPacket.QueryGroupInfo(qqAccount, internalId, sessionKey).sendAndExpect<RawGroupInfo>().parseBy(this@GroupImpl).also { info = it }
     }
 
-    override suspend fun quit(): QuitGroupResponse = bot.withSession {
-        GroupPacket.QuitGroup(qqAccount, sessionKey, internalId).sendAndExpect()
+    override suspend fun quit(): Boolean = withTIMPCBot {
+        GroupPacket.QuitGroup(qqAccount, sessionKey, internalId).sendAndExpect<GroupPacket.QuitGroupResponse>().isSuccess
     }
 
     @UseExperimental(MiraiInternalAPI::class)
@@ -84,25 +105,45 @@ internal data class GroupImpl internal constructor(override val bot: Bot, val gr
     override fun toString(): String = "Group(${this.id})"
 }
 
-@Suppress("FunctionName", "NOTHING_TO_INLINE")
-internal inline fun CoroutineScope.QQ(bot: Bot, id: UInt, coroutineContext: CoroutineContext): QQ = QQImpl(bot, id, coroutineContext).apply { launch { startUpdater() } }
-
 @PublishedApi
-internal data class QQImpl @PublishedApi internal constructor(override val bot: Bot, override val id: UInt, override val coroutineContext: CoroutineContext) :
+internal class QQImpl @PublishedApi internal constructor(bot: TIMPCBot, override val id: Long, override val coroutineContext: CoroutineContext) :
     ContactImpl(),
     QQ, CoroutineScope {
+    override val bot: TIMPCBot by bot.unsafeWeakRef()
+
     override suspend fun sendMessage(message: MessageChain) =
         bot.sendPacket(SendFriendMessagePacket(bot.qqAccount, id, bot.sessionKey, message))
 
-    override suspend fun queryProfile(): Profile = bot.withSession {
+    override suspend fun uploadImage(image: ExternalImage): ImageId = withTIMPCBot {
+        FriendImagePacket.RequestImageId(qqAccount, sessionKey, id, image).sendAndExpect<FriendImageResponse>().let {
+            when (it) {
+                is FriendImageUKey -> {
+                    Http.postImage(
+                        htcmd = "0x6ff0070",
+                        uin = bot.qqAccount,
+                        groupId = null,
+                        uKeyHex = it.uKey.toUHexString(""),
+                        imageInput = image.input,
+                        inputSize = image.inputSize
+                    )
+                    it.imageId
+                }
+                is FriendImageAlreadyExists -> it.imageId
+                is FriendImageOverFileSizeMax -> throw OverFileSizeMaxException()
+                else -> error("This shouldn't happen")
+            }
+        }
+    }
+
+    override suspend fun queryProfile(): Profile = withTIMPCBot {
         RequestProfileDetailsPacket(bot.qqAccount, id, sessionKey).sendAndExpect<RequestProfileDetailsResponse>().profile
     }
 
-    override suspend fun queryPreviousNameList(): PreviousNameList = bot.withSession {
+    override suspend fun queryPreviousNameList(): PreviousNameList = withTIMPCBot {
         QueryPreviousNamePacket(bot.qqAccount, sessionKey, id).sendAndExpect()
     }
 
-    override suspend fun queryRemark(): FriendNameRemark = bot.withSession {
+    override suspend fun queryRemark(): FriendNameRemark = withTIMPCBot {
         QueryFriendRemarkPacket(bot.qqAccount, sessionKey, id).sendAndExpect()
     }
 
@@ -114,10 +155,6 @@ internal data class QQImpl @PublishedApi internal constructor(override val bot:
     override fun toString(): String = "QQ(${this.id})"
 }
 
-@Suppress("FunctionName", "NOTHING_TO_INLINE")
-internal inline fun Group.Member(delegate: QQ, permission: MemberPermission, coroutineContext: CoroutineContext): Member =
-    MemberImpl(delegate, this, permission, coroutineContext).apply { launch { startUpdater() } }
-
 /**
  * 群成员
  */
@@ -130,13 +167,13 @@ internal data class MemberImpl(
 ) : QQ by delegate, CoroutineScope, Member, ContactImpl() {
     override fun toString(): String = "Member(id=${this.id}, group=${group.id}, permission=$permission)"
 
-    override suspend fun mute(durationSeconds: Int): Boolean = bot.withSession {
+    override suspend fun mute(durationSeconds: Int): Boolean = withTIMPCBot {
         require(durationSeconds > 0) { "duration must be greater than 0 second" }
         require(durationSeconds <= 30 * 24 * 3600) { "duration must be no more than 30 days" }
 
         if (permission == MemberPermission.OWNER) return false
         val operator = group.getMember(bot.qqAccount)
-        check(operator.id != id) { "The bot is the owner of group ${group.id.toLong()}, it cannot mute itself!" }
+        check(operator.id != id) { "The bot is the owner of group ${group.id}, it cannot mute itself!" }
         when (operator.permission) {
             MemberPermission.MEMBER -> return false
             MemberPermission.ADMINISTRATOR -> if (permission == MemberPermission.ADMINISTRATOR) return false
@@ -153,7 +190,7 @@ internal data class MemberImpl(
         // TODO: 2019/12/6 更新群成员信息
     }
 
-    override suspend fun unmute(): Unit = bot.withSession {
+    override suspend fun unmute(): Unit = withTIMPCBot {
         GroupPacket.Mute(qqAccount, group.internalId, sessionKey, id, 0u).sendAndExpect<GroupPacket.MuteResponse>()
     }
 }

+ 61 - 88
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/TIMBotNetworkHandler.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/TIMBotNetworkHandler.kt

@@ -1,30 +1,28 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE")
 
-package net.mamoe.mirai.network.protocol.timpc
+package net.mamoe.mirai.timpc.network
 
 import kotlinx.coroutines.*
-import kotlinx.coroutines.sync.Mutex
-import kotlinx.coroutines.sync.withLock
 import kotlinx.io.core.*
 import kotlinx.io.pool.useInstance
 import net.mamoe.mirai.Bot
-import net.mamoe.mirai.event.*
-import net.mamoe.mirai.event.events.BeforePacketSendEvent
+import net.mamoe.mirai.event.BroadcastControllable
+import net.mamoe.mirai.event.Cancellable
+import net.mamoe.mirai.event.Subscribable
+import net.mamoe.mirai.event.broadcast
 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.network.BotNetworkHandler
-import net.mamoe.mirai.network.BotSession
-import net.mamoe.mirai.network.protocol.timpc.handler.DataPacketSocketAdapter
-import net.mamoe.mirai.network.protocol.timpc.handler.TemporaryPacketHandler
-import net.mamoe.mirai.network.protocol.timpc.packet.*
-import net.mamoe.mirai.network.protocol.timpc.packet.login.*
+import net.mamoe.mirai.network.data.LoginResult
+import net.mamoe.mirai.network.data.Packet
 import net.mamoe.mirai.qqAccount
-import net.mamoe.mirai.utils.OnlineStatus
-import net.mamoe.mirai.utils.currentBotConfiguration
+import net.mamoe.mirai.timpc.TIMPCBot
+import net.mamoe.mirai.timpc.network.handler.DataPacketSocketAdapter
+import net.mamoe.mirai.timpc.network.handler.TemporaryPacketHandler
+import net.mamoe.mirai.timpc.network.packet.*
+import net.mamoe.mirai.timpc.network.packet.login.*
+import net.mamoe.mirai.utils.*
 import net.mamoe.mirai.utils.io.*
 import kotlin.coroutines.CoroutineContext
-import kotlin.properties.Delegates
 import kotlin.random.Random
 
 /**
@@ -39,9 +37,9 @@ internal expect val NetworkDispatcher: CoroutineDispatcher
  *
  * @see BotNetworkHandler
  */
-internal class TIMBotNetworkHandler internal constructor(coroutineContext: CoroutineContext, override val bot: Bot) :
-    BotNetworkHandler<TIMBotNetworkHandler.BotSocketAdapter>, CoroutineScope {
-
+internal class TIMBotNetworkHandler internal constructor(coroutineContext: CoroutineContext, bot: TIMPCBot) :
+    BotNetworkHandler(), CoroutineScope {
+    override val bot: TIMPCBot by bot.unsafeWeakRef()
     override val supervisor: CompletableJob = SupervisorJob(coroutineContext[Job])
 
     override val coroutineContext: CoroutineContext =
@@ -50,52 +48,43 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
                 ?: "an unnamed coroutine"} under TIMBotNetworkHandler", e)
         } + supervisor
 
-    override lateinit var socket: BotSocketAdapter
+    lateinit var socket: BotSocketAdapter
         private set
 
-    private val temporaryPacketHandlers = mutableListOf<TemporaryPacketHandler<*, *>>()
-    private val handlersLock = Mutex()
+    internal val temporaryPacketHandlers = LockFreeLinkedList<TemporaryPacketHandler<*, *>>()
 
     private var heartbeatJob: Job? = null
 
-    suspend fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*, *>) {
-        handlersLock.withLock {
-            temporaryPacketHandlers.add(temporaryPacketHandler)
-        }
-        temporaryPacketHandler.send(this.session)
-    }
+    override suspend fun login() {
 
-    override suspend fun login(): LoginResult {
-        return withContext(this.coroutineContext) {
-            TIMProtocol.SERVER_IP.sortedBy { Random.nextInt() }.forEach { ip ->
-                bot.logger.info("Connecting server $ip")
-                try {
-                    withTimeout(3000) {
-                        socket = BotSocketAdapter(ip)
-                    }
-                } catch (e: Exception) {
-                    return@withContext LoginResult.NETWORK_UNAVAILABLE
+        TIMProtocol.SERVER_IP.sortedBy { Random.nextInt() }.forEach { ip ->
+            bot.logger.info("Connecting server $ip")
+            try {
+                withTimeout(3000) {
+                    socket = BotSocketAdapter(ip)
                 }
+            } catch (e: Exception) {
+                throw  LoginFailedException(LoginResult.NETWORK_UNAVAILABLE)
+            }
 
-                loginResult = CompletableDeferred()
+            loginResult = CompletableDeferred()
 
-                socket.resendTouch().takeIf { it != LoginResult.TIMEOUT }?.let { return@withContext it }
+            val result = socket.resendTouch() ?: return // success
+            result.takeIf { it != LoginResult.TIMEOUT }?.let { throw  LoginFailedException(it) }
 
-                bot.logger.warning("Timeout. Retrying next server")
+            bot.logger.warning("Timeout. Retrying next server")
 
-                socket.close()
-            }
-            return@withContext LoginResult.TIMEOUT
+            socket.close()
         }
+        throw  LoginFailedException(LoginResult.TIMEOUT)
     }
 
-    internal var loginResult: CompletableDeferred<LoginResult> = CompletableDeferred()
-
-    override lateinit var session: BotSession
+    internal var loginResult: CompletableDeferred<LoginResult?> = CompletableDeferred()
 
     //private | internal
 
-    private var sessionKey: SessionKey by Delegates.notNull()
+    private var _sessionKey: SessionKey? = null
+    internal val sessionKey: SessionKey get() = _sessionKey ?: error("sessionKey is not yet initialized")
 
     override suspend fun awaitDisconnection() {
         heartbeatJob?.join()
@@ -187,43 +176,38 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
             }
         }
 
-        internal suspend fun resendTouch(): LoginResult /* = coroutineScope */ {
+        internal suspend fun resendTouch(): LoginResult? /* = coroutineScope */ {
             loginHandler?.close()
 
             loginHandler = LoginHandler()
 
 
-            val expect = expectPacket<TouchPacket.TouchResponse>()
-            launch { processReceive() }
-            launch {
-                if (withTimeoutOrNull(currentBotConfiguration().touchTimeoutMillis) { expect.join() } == null) {
-                    loginResult.complete(LoginResult.TIMEOUT)
+            expectingTouchResponse = Job(supervisor)
+            try {
+                launch { processReceive() }
+                launch {
+                    if (withTimeoutOrNull(currentBotConfiguration().touchTimeoutMillis) { expectingTouchResponse!!.join() } == null) {
+                        loginResult.complete(LoginResult.TIMEOUT)
+                    }
                 }
-            }
-            sendPacket(TouchPacket(bot.qqAccount, serverIp, false))
+                sendPacket(TouchPacket(bot.qqAccount, serverIp, false))
 
-            return loginResult.await()
-        }
-
-        private suspend inline fun <reified P : Packet> expectPacket(): CompletableDeferred<P> {
-            val receiving = CompletableDeferred<P>(coroutineContext[Job])
-            subscribe<ServerPacketReceivedEvent<*>> {
-                if (it.packet is P && it.bot === bot) {
-                    receiving.complete(it.packet)
-                    ListeningStatus.STOPPED
-                } else
-                    ListeningStatus.LISTENING
+                return loginResult.await()
+            } finally {
+                expectingTouchResponse = null
             }
-            return receiving
         }
 
+        private var expectingTouchResponse: CompletableJob? = null
+
         private suspend fun <TPacket : Packet> handlePacket0(
             sequenceId: UShort,
             packet: TPacket,
             factory: PacketFactory<TPacket, *>
         ) {
-            if (ServerPacketReceivedEvent(bot, packet).broadcast().cancelled)
-                return
+            if (packet is TouchPacket.TouchResponse) {
+                expectingTouchResponse?.complete()
+            }
 
             if (!packet::class.annotations.filterIsInstance<NoLog>().any()) {
                 if ((packet as? BroadcastControllable)?.shouldBroadcast != false) {
@@ -236,12 +220,10 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
                 is Subscribable -> if ((packet as? BroadcastControllable)?.shouldBroadcast != false) packet.broadcast()
             }
 
-            // Remove first to release the lock
-            handlersLock.withLock {
-                temporaryPacketHandlers.filter { it.filter(session, packet, sequenceId) }
-                    .also { temporaryPacketHandlers.removeAll(it) }
-            }.forEach {
-                it.doReceiveCatchingExceptions(packet)
+            temporaryPacketHandlers.forEach {
+                if (it.filter(packet, sequenceId) && temporaryPacketHandlers.remove(it)) {
+                    it.doReceivePassingExceptionsToDeferred(packet)
+                }
             }
 
             if (factory is SessionPacketFactory<*>) {
@@ -256,10 +238,6 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
         internal suspend fun sendPacket(packet: OutgoingPacket): Unit = withContext(coroutineContext + CoroutineName("sendPacket")) {
             check(channel.isOpen) { "channel is not open" }
 
-            if (BeforePacketSendEvent(bot, packet).broadcast().cancelled) {
-                return@withContext
-            }
-
             packet.delegate.use { built ->
                 val buffer = IoBuffer.Pool.borrow()
                 try {
@@ -291,8 +269,6 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
                 bot.logger.verbose("Packet sent:     ${it.name}")
             }
 
-            PacketSentEvent(bot, packet).broadcast()
-
             Unit
         }
 
@@ -466,8 +442,8 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
                 }
 
                 is RequestSessionPacket.SessionKeyResponse -> {
-                    sessionKey = packet.sessionKey
-                    bot.logger.info("sessionKey = ${sessionKey.value.toUHexString()}")
+                    _sessionKey = packet.sessionKey
+                    bot.logger.info("sessionKey = ${packet.sessionKey.value.toUHexString()}")
 
                     setOnlineStatus(OnlineStatus.ONLINE)//required
                 }
@@ -475,14 +451,11 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
                 is ChangeOnlineStatusPacket.ChangeOnlineStatusResponse -> {
                     BotLoginSucceedEvent(bot).broadcast()
 
-
-                    session = BotSession(sessionKey)
-
                     val configuration = currentBotConfiguration()
                     heartbeatJob = [email protected] {
                         while (socket.isOpen) {
                             delay(configuration.heartbeatPeriodMillis)
-                            with(session) {
+                            with(bot) {
                                 class HeartbeatTimeoutException : CancellationException("heartbeat timeout")
 
                                 if (withTimeoutOrNull(configuration.heartbeatTimeoutMillis) {
@@ -500,7 +473,7 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
                     }
 
                     bot.logger.info("Successfully logged in")
-                    loginResult.complete(LoginResult.SUCCESS)
+                    loginResult.complete(null)
                     this.close()//The LoginHandler is useless since then
                 }
             }

+ 1 - 1
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/TIMProtocol.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/TIMProtocol.kt

@@ -1,6 +1,6 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE")
 
-package net.mamoe.mirai.network.protocol.timpc
+package net.mamoe.mirai.timpc.network
 
 import net.mamoe.mirai.utils.io.hexToBytes
 import net.mamoe.mirai.utils.solveIpAddress

+ 1 - 6
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/handler/DataPacketSocketAdapter.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/handler/DataPacketSocketAdapter.kt

@@ -1,14 +1,9 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE")
 
-package net.mamoe.mirai.network.protocol.timpc.handler
+package net.mamoe.mirai.timpc.network.handler
 
 import kotlinx.io.core.Closeable
 import net.mamoe.mirai.Bot
-import net.mamoe.mirai.event.events.ServerPacketReceivedEvent
-import net.mamoe.mirai.network.BotSession
-import net.mamoe.mirai.network.protocol.timpc.TIMBotNetworkHandler
-import net.mamoe.mirai.network.protocol.timpc.packet.OutgoingPacket
-import net.mamoe.mirai.utils.MiraiInternalAPI
 import net.mamoe.mirai.utils.io.PlatformDatagramChannel
 
 /**

+ 37 - 0
mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/handler/TemporaryPacketHandler.kt

@@ -0,0 +1,37 @@
+@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
+
+package net.mamoe.mirai.timpc.network.handler
+
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.withContext
+import net.mamoe.mirai.network.data.Packet
+import kotlin.coroutines.CoroutineContext
+import kotlin.reflect.KClass
+
+internal class TemporaryPacketHandler<P : Packet, R>(
+    private val expectationClass: KClass<P>,
+    private val deferred: CompletableDeferred<R>,
+    private val checkSequence: UShort? = null,
+    /**
+     * 调用者的 [CoroutineContext]. 包处理过程将会在这个 context 下运行
+     */
+    private val callerContext: CoroutineContext,
+    private val handler: suspend (P) -> R
+) {
+    internal fun filter(packet: Packet, sequenceId: UShort): Boolean =
+        expectationClass.isInstance(packet) && if (checkSequence != null) sequenceId == checkSequence else true
+
+    internal suspend inline fun doReceivePassingExceptionsToDeferred(packet: Packet) {
+        @Suppress("UNCHECKED_CAST")
+        val ret = try {
+            withContext(callerContext) {
+                handler(packet as P)
+            }
+        } catch (e: Throwable) {
+            deferred.completeExceptionally(e)
+            return
+        }
+        deferred.complete(ret)
+    }
+}
+

+ 1 - 1
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/Annotations.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/Annotations.kt

@@ -1,6 +1,6 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE", "unused")
 
-package net.mamoe.mirai.network.protocol.timpc.packet
+package net.mamoe.mirai.timpc.network.packet
 
 /**
  * 包的最后一次修改时间, 和分析时使用的 TIM 版本

+ 11 - 2
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/Decrypters.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/Decrypters.kt

@@ -1,8 +1,12 @@
-package net.mamoe.mirai.network.protocol.timpc.packet
+package net.mamoe.mirai.timpc.network.packet
 
+import kotlinx.io.core.BytePacketBuilder
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.IoBuffer
+import kotlinx.io.core.writeFully
 import net.mamoe.mirai.utils.decryptBy
+import net.mamoe.mirai.utils.encryptBy
+import net.mamoe.mirai.utils.io.encryptAndWrite
 
 
 /**
@@ -15,6 +19,7 @@ internal inline class SessionKey(override val value: ByteArray) : DecrypterByteA
 /**
  * [ByteArray] 解密器
  */
+@PublishedApi
 internal interface DecrypterByteArray : Decrypter {
     val value: ByteArray
     override fun decrypt(input: ByteReadPacket): ByteReadPacket = input.decryptBy(value)
@@ -50,4 +55,8 @@ internal interface Decrypter {
     operator fun plus(another: Decrypter): Decrypter = LinkedDecrypter { another.decrypt(this.decrypt(it)) }
 }
 
-internal interface DecrypterType<D : Decrypter>
+internal interface DecrypterType<D : Decrypter>
+
+@PublishedApi
+internal inline fun BytePacketBuilder.encryptAndWrite(key: DecrypterByteArray, encoder: BytePacketBuilder.() -> Unit) =
+    this.encryptAndWrite(key.value, encoder)

+ 6 - 12
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/OutgoingPacket.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/OutgoingPacket.kt

@@ -1,13 +1,13 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "unused", "MemberVisibilityCanBePrivate")
 
-package net.mamoe.mirai.network.protocol.timpc.packet
+package net.mamoe.mirai.timpc.network.packet
 
 import kotlinx.io.core.*
 import kotlinx.serialization.SerializationStrategy
 import kotlinx.serialization.protobuf.ProtoBuf
 import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
-import net.mamoe.mirai.utils.io.encryptAndWrite
+import net.mamoe.mirai.network.data.Packet
+import net.mamoe.mirai.timpc.network.TIMProtocol
 import net.mamoe.mirai.utils.io.hexToBytes
 import net.mamoe.mirai.utils.io.writeQQ
 import kotlin.contracts.ExperimentalContracts
@@ -39,13 +39,11 @@ internal abstract class SessionPacketFactory<TPacket : Packet> : PacketFactory<T
     /**
      * 在 [BotNetworkHandler] 下处理这个包. 广播事件等.
      */
-    open suspend fun BotNetworkHandler<*>.handlePacket(packet: TPacket) {}
+    open suspend fun BotNetworkHandler.handlePacket(packet: TPacket) {}
 }
 
 /**
  * 构造一个待发送给服务器的数据包.
- *
- * 若不提供参数 [id], 则会通过注解 [AnnotatedId] 获取 id.
  */
 @UseExperimental(ExperimentalContracts::class)
 @JvmOverloads
@@ -76,13 +74,11 @@ internal inline fun PacketFactory<*, *>.buildOutgoingPacket(
 
 /**
  * 构造一个待发送给服务器的会话数据包.
- *
- * 若不提供参数 [id], 则会通过注解 [AnnotatedId] 获取 id.
  */
 @UseExperimental(ExperimentalContracts::class)
 @JvmOverloads
 internal inline fun PacketFactory<*, *>.buildSessionPacket(
-    bot: UInt,
+    bot: Long,
     sessionKey: SessionKey,
     name: String? = null,
     id: PacketId = this.id,
@@ -105,13 +101,11 @@ internal inline fun PacketFactory<*, *>.buildSessionPacket(
 
 /**
  * 构造一个待发送给服务器的会话数据包.
- *
- * 若不提供参数 [id], 则会通过注解 [AnnotatedId] 获取 id.
  */
 @UseExperimental(ExperimentalContracts::class)
 @JvmOverloads
 internal fun <T> PacketFactory<*, *>.buildSessionProtoPacket(
-    bot: UInt,
+    bot: Long,
     sessionKey: SessionKey,
     name: String? = null,
     id: PacketId = this.id,

+ 2 - 6
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/Packet.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/Packet.kt

@@ -1,14 +1,10 @@
-package net.mamoe.mirai.network.protocol.timpc.packet
+package net.mamoe.mirai.timpc.network.packet
 
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.readBytes
+import net.mamoe.mirai.network.data.Packet
 import net.mamoe.mirai.utils.io.toUHexString
 
-/**
- * 一个包的数据 (body)
- */
-interface Packet
-
 /**
  * 被忽略的数据包.
  */

+ 6 - 5
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/PacketFactory.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/PacketFactory.kt

@@ -1,6 +1,6 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
 
-package net.mamoe.mirai.network.protocol.timpc.packet
+package net.mamoe.mirai.timpc.network.packet
 
 import kotlinx.atomicfu.atomic
 import kotlinx.io.core.ByteReadPacket
@@ -10,6 +10,7 @@ import kotlinx.io.pool.useInstance
 import kotlinx.serialization.DeserializationStrategy
 import kotlinx.serialization.protobuf.ProtoBuf
 import net.mamoe.mirai.network.BotNetworkHandler
+import net.mamoe.mirai.network.data.Packet
 import net.mamoe.mirai.utils.io.ByteArrayPool
 import net.mamoe.mirai.utils.io.debugPrint
 import net.mamoe.mirai.utils.io.read
@@ -36,7 +37,7 @@ internal abstract class PacketFactory<out TPacket : Packet, TDecrypter : Decrypt
     /**
      * **解码**服务器的回复数据包
      */
-    abstract suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): TPacket
+    abstract suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): TPacket
 
     @Suppress("DEPRECATION")
     fun <T> ByteReadPacket.decodeProtoPacket(
@@ -69,7 +70,7 @@ internal abstract class PacketFactory<out TPacket : Packet, TDecrypter : Decrypt
 }
 
 internal object UnknownPacketFactory : SessionPacketFactory<UnknownPacket>() {
-    override suspend fun BotNetworkHandler<*>.handlePacket(packet: UnknownPacket) {
+    override suspend fun BotNetworkHandler.handlePacket(packet: UnknownPacket) {
         ByteArrayPool.useInstance {
             packet.body.readAvailable(it)
             bot.logger.debug("UnknownPacket(${packet.id.value.toUHexString()}) = " + it.toUHexString())
@@ -80,7 +81,7 @@ internal object UnknownPacketFactory : SessionPacketFactory<UnknownPacket>() {
     override suspend fun ByteReadPacket.decode(
         id: PacketId,
         sequenceId: UShort,
-        handler: BotNetworkHandler<*>
+        handler: BotNetworkHandler
     ): UnknownPacket {
         return UnknownPacket(id, this)
     }
@@ -90,6 +91,6 @@ internal object IgnoredPacketFactory : SessionPacketFactory<IgnoredPacket>() {
     override suspend fun ByteReadPacket.decode(
         id: PacketId,
         sequenceId: UShort,
-        handler: BotNetworkHandler<*>
+        handler: BotNetworkHandler
     ): IgnoredPacket = IgnoredPacket(id)
 }

+ 5 - 5
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/PacketId.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/PacketId.kt

@@ -1,11 +1,11 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
 
-package net.mamoe.mirai.network.protocol.timpc.packet
+package net.mamoe.mirai.timpc.network.packet
 
-import net.mamoe.mirai.network.protocol.timpc.packet.action.*
-import net.mamoe.mirai.network.protocol.timpc.packet.event.EventPacketFactory
-import net.mamoe.mirai.network.protocol.timpc.packet.event.FriendOnlineStatusChangedPacket
-import net.mamoe.mirai.network.protocol.timpc.packet.login.*
+import net.mamoe.mirai.timpc.network.packet.action.*
+import net.mamoe.mirai.timpc.network.packet.event.EventPacketFactory
+import net.mamoe.mirai.timpc.network.packet.event.FriendOnlineStatusChangedPacket
+import net.mamoe.mirai.timpc.network.packet.login.*
 import net.mamoe.mirai.utils.io.toUHexString
 
 

+ 25 - 36
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/AddContact.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/AddContact.kt

@@ -1,15 +1,16 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.action
+package net.mamoe.mirai.timpc.network.packet.action
 
 import kotlinx.io.core.*
 import net.mamoe.mirai.contact.QQ
 import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
-import net.mamoe.mirai.network.protocol.timpc.packet.*
-import net.mamoe.mirai.network.protocol.timpc.packet.event.EventPacket
+import net.mamoe.mirai.network.data.EventPacket
+import net.mamoe.mirai.network.data.Packet
+import net.mamoe.mirai.network.data.PreviousNameList
+import net.mamoe.mirai.timpc.network.TIMProtocol
+import net.mamoe.mirai.timpc.network.packet.*
 import net.mamoe.mirai.utils.io.*
-import net.mamoe.mirai.withSession
 
 
 /**
@@ -22,9 +23,9 @@ import net.mamoe.mirai.withSession
 @PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)")
 internal object QueryPreviousNamePacket : SessionPacketFactory<PreviousNameList>() {
     operator fun invoke(
-        bot: UInt,
+        bot: Long,
         sessionKey: SessionKey,
-        target: UInt
+        target: Long
     ): OutgoingPacket = buildSessionPacket(bot, sessionKey) {
         writeZero(2)
         writeQQ(bot)
@@ -41,7 +42,7 @@ internal object QueryPreviousNamePacket : SessionPacketFactory<PreviousNameList>
     //      [00 00 00 10] 68 69 6D 31 38 38 E7 9A 84 E5 B0 8F 64 69 63 6B
     //      [00 00 00 0F] E4 B8 B6 E6 9A 97 E8 A3 94 E5 89 91 E9 AD 94
 
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): PreviousNameList {
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): PreviousNameList {
         // 00 00 00 01 00 00 00 0F E8 87 AA E5 8A A8 E9 A9 BE E9 A9 B6 31 2E 33
 
         val count = readUInt().toInt()
@@ -54,19 +55,6 @@ internal object QueryPreviousNamePacket : SessionPacketFactory<PreviousNameList>
     }
 }
 
-/**
- * 曾用名列表
- *
- * 曾用名可能是:
- * - 昵称
- * - 共同群内的群名片
- */
-class PreviousNameList(
-    list: List<String>
-) : Packet, List<String> by list {
-    override fun toString(): String = this.joinToString(prefix = "PreviousNameList(", postfix = ")", separator = ", ")
-}
-
 // 需要验证消息
 // 0065 发送 03 07 57 37 E8
 // 0065 接受 03 07 57 37 E8 10 40 00 00 10 14 20 00 00 00 00 00 00 00 01 00 00 00 00 00
@@ -79,20 +67,21 @@ class PreviousNameList(
 @PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)")
 internal object CanAddFriendPacket : SessionPacketFactory<CanAddFriendResponse>() {
     operator fun invoke(
-        bot: UInt,
-        qq: UInt,
+        bot: Long,
+        qq: Long,
         sessionKey: SessionKey
     ): OutgoingPacket = buildSessionPacket(bot, sessionKey) {
         writeQQ(qq)
     }
 
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): CanAddFriendResponse =
-        handler.bot.withSession {
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): CanAddFriendResponse =
+        with(handler.bot) {
             if (remaining > 20) {//todo check
-                return CanAddFriendResponse.AlreadyAdded(readUInt().qq())
+                return CanAddFriendResponse.AlreadyAdded(readQQ().qq())
             }
-            val qq: QQ = readUInt().qq()
+            val qq: QQ = readQQ().qq()
 
+            readUByteLVByteArray()
             // debugDiscardExact(1)
 
             return when (val state = readUByte().toUInt()) {
@@ -148,7 +137,7 @@ internal sealed class CanAddFriendResponse : EventPacket {
 接受 03 00 00 00 00 01 30 5D 12 93 30 00 14 00 00 00 00 10 30 36 35 39 E4 B8 80 E7 BE 8E E5 A4 A9 E9 9D 99 02 0A 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 1E
  */
 
-inline class FriendAdditionKey(val value: IoBuffer)
+internal inline class FriendAdditionKey(val value: IoBuffer)
 
 /**
  * 请求一个 32 位 Key, 在添加好友时发出
@@ -156,8 +145,8 @@ inline class FriendAdditionKey(val value: IoBuffer)
 @PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)")
 internal object RequestFriendAdditionKeyPacket : SessionPacketFactory<RequestFriendAdditionKeyPacket.Response>() {
     operator fun invoke(
-        bot: UInt,
-        qq: UInt,
+        bot: Long,
+        qq: Long,
         sessionKey: SessionKey
     ) = buildSessionPacket(bot, sessionKey) {
         //01 00 01 02 B3 74 F6
@@ -165,7 +154,7 @@ internal object RequestFriendAdditionKeyPacket : SessionPacketFactory<RequestFri
         writeQQ(qq)
     }
 
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): Response {
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): Response {
         //01 00 01 00 00 20 01 C2 76 47 98 38 A1 FF AB 64 04 A9 81 1F CC 2B 2B A6 29 FC 97 80 A6 90 2D 26 C8 37 EE 1D 8A FA
         discardExact(4)
         return Response(FriendAdditionKey(readIoBuffer(readUShort().toInt())))
@@ -183,8 +172,8 @@ internal object AddFriendPacket : SessionPacketFactory<AddFriendPacket.Response>
     @PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)")
     @Suppress("FunctionName")
     fun RequestAdd(
-        bot: UInt,
-        qq: UInt,
+        bot: Long,
+        qq: Long,
         sessionKey: SessionKey,
         /**
          * 验证消息
@@ -250,13 +239,13 @@ internal object AddFriendPacket : SessionPacketFactory<AddFriendPacket.Response>
     @Suppress("FunctionName")
     @PacketVersion(date = "2019.11.20", timVersion = "2.3.2 (21173)")
     fun Approve(
-        bot: UInt,
+        bot: Long,
         sessionKey: SessionKey,
         /**
          * 好友列表分组的组的 ID. "我的好友" 为 0
          */
         friendListId: Short,
-        qq: UInt,
+        qq: Long,
         /**
          * 备注. 不设置则需要为 `null` TODO 需要确认是否还需发送一个设置备注包. 因为测试时若有备注则会多发一个包并且包里面有所设置的备注
          */
@@ -284,7 +273,7 @@ internal object AddFriendPacket : SessionPacketFactory<AddFriendPacket.Response>
     }
 
 
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): Response {
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): Response {
         //02 02 B3 74 F6 00 //02 B3 74 F6 是QQ号
         return Response
     }

+ 17 - 51
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/FriendImage.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/FriendImage.kt

@@ -1,53 +1,19 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.action
+package net.mamoe.mirai.timpc.network.packet.action
 
 import kotlinx.io.charsets.Charsets
 import kotlinx.io.core.*
-import net.mamoe.mirai.contact.QQ
-import net.mamoe.mirai.message.ImageId
-import net.mamoe.mirai.message.ImageId0x06
-import net.mamoe.mirai.message.requireLength
+import net.mamoe.mirai.message.data.ImageId
+import net.mamoe.mirai.message.data.ImageId0x06
+import net.mamoe.mirai.message.data.requireLength
 import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
-import net.mamoe.mirai.network.protocol.timpc.packet.*
-import net.mamoe.mirai.network.protocol.timpc.packet.event.EventPacket
-import net.mamoe.mirai.network.qqAccount
-import net.mamoe.mirai.qqAccount
+import net.mamoe.mirai.network.data.EventPacket
+import net.mamoe.mirai.network.data.ImageLink
+import net.mamoe.mirai.timpc.network.TIMProtocol
+import net.mamoe.mirai.timpc.network.packet.*
 import net.mamoe.mirai.utils.ExternalImage
-import net.mamoe.mirai.utils.Http
 import net.mamoe.mirai.utils.io.*
-import net.mamoe.mirai.withSession
-
-
-/**
- * 上传图片
- * 挂起直到上传完成或失败
- *
- * 在 JVM 下, `SendImageUtilsJvm.kt` 内有多个捷径函数
- *
- * @throws OverFileSizeMaxException 如果文件过大, 服务器拒绝接收时
- */
-suspend fun QQ.uploadImage(image: ExternalImage): ImageId = bot.withSession {
-    FriendImagePacket.RequestImageId(qqAccount, sessionKey, id, image).sendAndExpect<FriendImageResponse>().let {
-        when (it) {
-            is FriendImageUKey -> {
-                Http.postImage(
-                    htcmd = "0x6ff0070",
-                    uin = bot.qqAccount,
-                    groupId = null,
-                    uKeyHex = it.uKey.toUHexString(""),
-                    imageInput = image.input,
-                    inputSize = image.inputSize
-                )
-                it.imageId
-            }
-            is FriendImageAlreadyExists -> it.imageId
-            is FriendImageOverFileSizeMax -> throw OverFileSizeMaxException()
-            else -> error("This shouldn't happen")
-        }
-    }
-}
 
 
 // region FriendImageResponse
@@ -58,7 +24,7 @@ internal interface FriendImageResponse : EventPacket
  * 图片数据地址.
  */
 // TODO: 2019/11/15 应该为 inline class, 但 kotlin 有 bug
-data class FriendImageLink(override inline val original: String) : FriendImageResponse, ImageLink {
+internal data class FriendImageLink(override inline val original: String) : FriendImageResponse, ImageLink {
     override fun toString(): String = "FriendImageLink($original)"
 }
 
@@ -96,9 +62,9 @@ internal object FriendImageOverFileSizeMax : FriendImageResponse {
 internal object FriendImagePacket : SessionPacketFactory<FriendImageResponse>() {
     @Suppress("FunctionName")
     fun RequestImageId(
-        bot: UInt,
+        bot: Long,
         sessionKey: SessionKey,
-        target: UInt,
+        target: Long,
         image: ExternalImage
     ): OutgoingPacket = buildSessionPacket(
         bot,
@@ -119,8 +85,8 @@ internal object FriendImagePacket : SessionPacketFactory<FriendImageResponse>()
             writeTV(0x08_01u)
 
             writeUVarIntLVPacket(tag = 0x12u, lengthOffset = { it + 1 }) {
-                writeTUVarint(0x08u, bot)
-                writeTUVarint(0x10u, target)
+                writeTUVarint(0x08u, bot.toUInt())
+                writeTUVarint(0x10u, target.toUInt())
                 writeTV(0x18_00u)
                 writeTLV(0x22u, image.md5)
                 writeTUVarint(0x28u, image.inputSize.toUInt())
@@ -150,7 +116,7 @@ internal object FriendImagePacket : SessionPacketFactory<FriendImageResponse>()
 
     @Suppress("FunctionName")
     fun RequestImageLink(
-        bot: UInt,
+        bot: Long,
         sessionKey: SessionKey,
         imageId: ImageId
     ): OutgoingPacket {
@@ -207,8 +173,8 @@ internal object FriendImagePacket : SessionPacketFactory<FriendImageResponse>()
 
             writeUByte(0x1Au)
             writeUByte(0x47u)
-            writeTUVarint(0x08u, bot)
-            writeTUVarint(0x10u, bot) // 这里实际上应该是这张图片来自哪个 QQ 号. 但传 bot 也没事.
+            writeTUVarint(0x08u, bot.toUInt())
+            writeTUVarint(0x10u, bot.toUInt()) // 这里实际上应该是这张图片来自哪个 QQ 号. 但传 bot 也没事.
             writeTLV(0x1Au, imageId.value.toByteArray(Charsets.ISO_8859_1))
             writeHex("20 02 30 04 38 20 40 FF 01 50 00 6A 05 32 36 39 33 33 78 01")
         }
@@ -217,7 +183,7 @@ internal object FriendImagePacket : SessionPacketFactory<FriendImageResponse>()
     override suspend fun ByteReadPacket.decode(
         id: PacketId,
         sequenceId: UShort,
-        handler: BotNetworkHandler<*>
+        handler: BotNetworkHandler
     ): FriendImageResponse {
 
         // 上传图片, 成功获取ID

+ 5 - 5
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/FriendList.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/FriendList.kt

@@ -1,12 +1,12 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.action
+package net.mamoe.mirai.timpc.network.packet.action
 
 import kotlinx.io.core.ByteReadPacket
 import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.protocol.timpc.packet.Packet
-import net.mamoe.mirai.network.protocol.timpc.packet.PacketId
-import net.mamoe.mirai.network.protocol.timpc.packet.SessionPacketFactory
+import net.mamoe.mirai.network.data.Packet
+import net.mamoe.mirai.timpc.network.packet.PacketId
+import net.mamoe.mirai.timpc.network.packet.SessionPacketFactory
 
 
 // 0001
@@ -26,7 +26,7 @@ import net.mamoe.mirai.network.protocol.timpc.packet.SessionPacketFactory
 internal inline class FriendListList(val delegate: List<FriendList>): Packet
 
 internal object QueryFriendListListPacket : SessionPacketFactory<FriendList>() {
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): FriendList {
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): FriendList {
         TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
     }
 }

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

@@ -1,14 +1,14 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.action
+package net.mamoe.mirai.timpc.network.packet.action
 
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.writeFully
 import kotlinx.io.core.writeUByte
 import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
-import net.mamoe.mirai.network.protocol.timpc.packet.*
-import net.mamoe.mirai.utils.io.encryptAndWrite
+import net.mamoe.mirai.network.data.Packet
+import net.mamoe.mirai.timpc.network.TIMProtocol
+import net.mamoe.mirai.timpc.network.packet.*
 import net.mamoe.mirai.utils.io.writeQQ
 
 /**
@@ -18,7 +18,7 @@ import net.mamoe.mirai.utils.io.writeQQ
  */
 internal object RequestAccountInfoPacket : SessionPacketFactory<RequestAccountInfoPacket.Response>() {
     operator fun invoke(
-        qq: UInt,
+        qq: Long,
         sessionKey: SessionKey
     ): OutgoingPacket = buildOutgoingPacket {
         writeQQ(qq)
@@ -35,5 +35,5 @@ internal object RequestAccountInfoPacket : SessionPacketFactory<RequestAccountIn
         override fun toString(): String = "RequestAccountInfoPacket.Response"
     }
 
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): Response = Response
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): Response = Response
 }

+ 10 - 51
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/GroupImage.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/GroupImage.kt

@@ -1,62 +1,21 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "RUNTIME_ANNOTATION_NOT_SUPPORTED")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.action
+package net.mamoe.mirai.timpc.network.packet.action
 
-import kotlinx.coroutines.withContext
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.serialization.SerialId
 import kotlinx.serialization.Serializable
-import net.mamoe.mirai.contact.Group
-import net.mamoe.mirai.contact.GroupId
 import net.mamoe.mirai.contact.GroupInternalId
-import net.mamoe.mirai.contact.withSession
-import net.mamoe.mirai.message.ImageId
-import net.mamoe.mirai.message.ImageId0x03
-import net.mamoe.mirai.message.requireLength
+import net.mamoe.mirai.message.data.ImageId0x03
+import net.mamoe.mirai.message.data.requireLength
 import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.protocol.timpc.packet.*
-import net.mamoe.mirai.network.protocol.timpc.packet.event.EventPacket
-import net.mamoe.mirai.qqAccount
+import net.mamoe.mirai.network.data.EventPacket
+import net.mamoe.mirai.network.data.ImageLink
+import net.mamoe.mirai.timpc.network.packet.*
+import net.mamoe.mirai.timpc.utils.assertUnreachable
 import net.mamoe.mirai.utils.ExternalImage
-import net.mamoe.mirai.utils.Http
-import net.mamoe.mirai.utils.assertUnreachable
 import net.mamoe.mirai.utils.io.toUHexString
 
-
-/**
- * 上传群图片
- * 挂起直到上传完成或失败
- *
- * 在 JVM 下, `SendImageUtilsJvm.kt` 内有多个捷径函数
- *
- * @throws OverFileSizeMaxException 如果文件过大, 服务器拒绝接收时
- */
-suspend fun Group.uploadImage(image: ExternalImage): ImageId = withSession {
-    val userContext = coroutineContext
-    val response = GroupImagePacket.RequestImageId(bot.qqAccount, internalId, image, sessionKey).sendAndExpect<GroupImageResponse>()
-
-    withContext(userContext) {
-        when (response) {
-            is ImageUploadInfo -> response.uKey?.let {
-                Http.postImage(
-                    htcmd = "0x6ff0071",
-                    uin = bot.qqAccount,
-                    groupId = GroupId(id),
-                    imageInput = image.input,
-                    inputSize = image.inputSize,
-                    uKeyHex = it.toUHexString("")
-                )
-            }
-
-            // TODO: 2019/11/17 超过大小的情况
-            //is Overfile -> throw OverFileSizeMaxException()
-            else -> assertUnreachable()
-        }
-    }
-
-    return image.groupImageId
-}
-
 internal interface GroupImageResponse : EventPacket
 
 // endregion
@@ -193,7 +152,7 @@ internal object GroupImagePacket : SessionPacketFactory<GroupImageResponse>() {
 
     @Suppress("FunctionName")
     fun RequestImageId(
-        bot: UInt,
+        bot: Long,
         groupInternalId: GroupInternalId,
         image: ExternalImage,
         sessionKey: SessionKey
@@ -215,7 +174,7 @@ internal object GroupImagePacket : SessionPacketFactory<GroupImageResponse>() {
     private val requestImageLinkHead = ubyteArrayOf(0x08u, 0x01u, 0x12u, 0x03u, 0x98u, 0x01u, 0x2u)
     @Suppress("FunctionName")
     fun RequestImageLink(
-        bot: UInt,
+        bot: Long,
         sessionKey: SessionKey,
         imageId: ImageId0x03
     ): OutgoingPacket {
@@ -242,7 +201,7 @@ internal object GroupImagePacket : SessionPacketFactory<GroupImageResponse>() {
         )
     }
 
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): GroupImageResponse {
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): GroupImageResponse {
 
         @Serializable
         data class GroupImageResponseProto(

+ 35 - 79
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/GroupPacket.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/GroupPacket.kt

@@ -1,88 +1,34 @@
 @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.action
+package net.mamoe.mirai.timpc.network.packet.action
 
 import kotlinx.io.core.*
-import net.mamoe.mirai.contact.*
-import net.mamoe.mirai.contact.internal.Member
-import net.mamoe.mirai.message.MessageChain
+import net.mamoe.mirai.contact.GroupInternalId
+import net.mamoe.mirai.contact.MemberPermission
+import net.mamoe.mirai.contact.groupInternalId
+import net.mamoe.mirai.message.data.MessageChain
 import net.mamoe.mirai.message.internal.toPacket
 import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
-import net.mamoe.mirai.network.protocol.timpc.packet.*
-import net.mamoe.mirai.utils.LockFreeLinkedList
-import net.mamoe.mirai.utils.MiraiInternalAPI
+import net.mamoe.mirai.network.data.Packet
+import net.mamoe.mirai.timpc.internal.RawGroupInfo
+import net.mamoe.mirai.timpc.network.TIMProtocol
+import net.mamoe.mirai.timpc.network.packet.*
+import net.mamoe.mirai.timpc.utils.unsupportedFlag
+import net.mamoe.mirai.timpc.utils.unsupportedType
 import net.mamoe.mirai.utils.io.*
-import net.mamoe.mirai.withSession
+import kotlin.collections.mutableMapOf
+import kotlin.collections.set
 
 
-/**
- * 群资料
- */
-@Suppress("MemberVisibilityCanBePrivate") // 将来使用
-class GroupInfo(
-    internal var _group: Group,
-    internal var _owner: Member,
-    internal var _name: String,
-    internal var _announcement: String,
-    internal var _members: ContactList<Member>
-) {
-    val group: Group get() = _group
-    val owner: Member get() = _owner
-    val name: String get() = _name
-    val announcement: String get() = _announcement
-    val members: ContactList<Member> get() = _members
-
-    override fun toString(): String =
-        "GroupInfo(id=${group.id}, owner=$owner, name=$name, announcement=$announcement, members=${members.idContentString}"
-}
-
 internal object GroupNotFound : GroupPacket.InfoResponse {
     override fun toString(): String = "GroupPacket.InfoResponse.GroupNotFound"
 }
 
-internal data class RawGroupInfo(
-    val group: UInt,
-    val owner: UInt,
-    val name: String,
-    val announcement: String,
-    /**
-     * 含群主
-     */
-    val members: Map<UInt, MemberPermission>
-) : GroupPacket.InfoResponse {
-
-    @UseExperimental(MiraiInternalAPI::class)
-    fun parseBy(group: Group): GroupInfo = group.bot.withSession {
-        val memberList = LockFreeLinkedList<Member>()
-        members.forEach { entry: Map.Entry<UInt, MemberPermission> ->
-            memberList.addLast(entry.key.qq().let { group.Member(it, entry.value, it.coroutineContext) })
-        }
-        return GroupInfo(
-            group,
-            [email protected]().let { group.Member(it, MemberPermission.OWNER, it.coroutineContext) },
-            [email protected],
-            [email protected],
-            ContactList(memberList)
-        )
-    }
-}
-
-/**
- * 退出群的返回
- */
-inline class QuitGroupResponse(private val _group: GroupInternalId?) : Packet, GroupPacket.GroupPacketResponse {
-    val group: GroupInternalId get() = _group ?: error("request failed")
-    val isSuccess: Boolean get() = _group != null
-
-    override fun toString(): String = "GroupPacket.QuitResponse"
-}
-
 @Suppress("FunctionName")
 internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketResponse>() {
     @PacketVersion(date = "2019.10.19", timVersion = "2.3.2 (21173)")
     fun Message(
-        bot: UInt,
+        bot: Long,
         groupInternalId: GroupInternalId,
         sessionKey: SessionKey,
         message: MessageChain
@@ -109,7 +55,7 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
      */
     @PacketVersion(date = "2019.11.28", timVersion = "2.3.2 (21173)")
     fun QuitGroup(
-        bot: UInt,
+        bot: Long,
         sessionKey: SessionKey,
         group: GroupInternalId
     ): OutgoingPacket = buildSessionPacket(bot, sessionKey, name = "GroupPacket.QuitGroup") {
@@ -122,7 +68,7 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
      */
     @PacketVersion(date = "2019.11.27", timVersion = "2.3.2 (21173)")
     fun QueryGroupInfo(
-        bot: UInt,
+        bot: Long,
         groupInternalId: GroupInternalId,
         sessionKey: SessionKey
     ): OutgoingPacket = buildSessionPacket(bot, sessionKey, name = "GroupPacket.QueryGroupInfo", headerSizeHint = 9) {
@@ -136,10 +82,10 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
      */
     @PacketVersion(date = "2019.12.2", timVersion = "2.3.2 (21173)")
     fun Mute(
-        bot: UInt,
+        bot: Long,
         groupInternalId: GroupInternalId,
         sessionKey: SessionKey,
-        target: UInt,
+        target: Long,
         /**
          * 0 为取消
          */
@@ -168,12 +114,22 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
 
     internal interface InfoResponse : Packet, GroupPacketResponse
 
+    /**
+     * 退出群的返回
+     */
+    class QuitGroupResponse(private val _group: GroupInternalId?) : Packet, GroupPacketResponse {
+        val group: GroupInternalId get() = _group ?: error("request failed")
+        val isSuccess: Boolean get() = _group != null
+
+        override fun toString(): String = "GroupPacket.QuitResponse"
+    }
+
     @PacketVersion(date = "2019.11.27", timVersion = "2.3.2 (21173)")
     @UseExperimental(ExperimentalStdlibApi::class)
     override suspend fun ByteReadPacket.decode(
         id: PacketId,
         sequenceId: UShort,
-        handler: BotNetworkHandler<*>
+        handler: BotNetworkHandler
     ): GroupPacketResponse {
         return when (val packetType = readUByte().toUInt()) {
             0x2Au -> MessageResponse
@@ -181,7 +137,7 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
 
             0x09u -> {
                 if (readByte().toInt() == 0) {
-                    QuitGroupResponse(readUInt().groupInternalId())
+                    QuitGroupResponse(readUInt().toLong().groupInternalId())
                 } else {
                     QuitGroupResponse(null)
                 }
@@ -218,10 +174,10 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
                         76 E4 B8 DD 00 00
                          */
                         discardExact(4) // group internal id
-                        val group = readUInt() // group id
+                        val group = readUInt().toLong() // group id
 
                         discardExact(13) //00 00 00 03 01 01 00 04 01  00 80 01 40
-                        val owner = readUInt()
+                        val owner = readUInt().toLong()
                         discardExact(22)
                         val groupName = readUByteLVString()
 
@@ -234,11 +190,11 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
 
                         discardExact(50)
 
-                        val stop = readUInt() // 标记读取群成员的结束
+                        val stop = readUInt().toLong() // 标记读取群成员的结束
                         discardExact(1) // 00
-                        val members = mutableMapOf<UInt, MemberPermission>()
+                        val members = mutableMapOf<Long, MemberPermission>()
                         do {
-                            val qq = readUInt()
+                            val qq = readUInt().toLong()
                             val status = readUShort().toInt() // 这个群成员的状态, 最后一 bit 为管理员权限. 这里面还包含其他状态
                             if (qq == owner) {
                                 continue

+ 3 - 3
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/HttpAPIAccessor.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/HttpAPIAccessor.kt

@@ -1,6 +1,6 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.action
+package net.mamoe.mirai.timpc.network.packet.action
 
 import io.ktor.client.HttpClient
 import io.ktor.client.request.post
@@ -17,7 +17,7 @@ import net.mamoe.mirai.contact.GroupId
 @Suppress("SpellCheckingInspection")
 internal suspend inline fun HttpClient.postImage(
     htcmd: String,
-    uin: UInt,
+    uin: Long,
     groupId: GroupId?,
     imageInput: Input,
     inputSize: Long,
@@ -30,7 +30,7 @@ internal suspend inline fun HttpClient.postImage(
             path("cgi-bin/httpconn")
 
             parameters["htcmd"] = htcmd
-            parameters["uin"] = uin.toLong().toString()
+            parameters["uin"] = uin.toString()
 
             if (groupId != null) parameters["groupcode"] = groupId.value.toLong().toString()
 

+ 3 - 23
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/Image.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/Image.kt

@@ -1,27 +1,7 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "unused", "NO_REFLECTION_IN_CLASS_PATH")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.action
+package net.mamoe.mirai.timpc.network.packet.action
 
-import io.ktor.client.request.get
-import kotlinx.io.core.ByteReadPacket
-import kotlinx.io.core.readBytes
-import net.mamoe.mirai.utils.Http
-
-/**
- * 图片文件过大
- */
-class OverFileSizeMaxException : IllegalStateException()
-
-interface ImageLink {
-    /**
-     * 原图
-     */
-    val original: String
-
-    suspend fun downloadAsByteArray(): ByteArray = download().readBytes()
-
-    suspend fun download(): ByteReadPacket = Http.get(original)
-}
 
 /*
 /**
@@ -32,8 +12,8 @@ interface ImageLink {
 @PacketVersion(date = "2019.10.26", timVersion = "2.3.2 (21173)")
 object SubmitImageFilenamePacket : PacketFactory {
     operator fun invoke(
-        bot: UInt,
-        target: UInt,
+        bot: Long,
+        target: Long,
         filename: String,
         sessionKey: SessionKey
     ): OutgoingPacket = buildOutgoingPacket {

+ 21 - 20
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/Profile.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/Profile.kt

@@ -1,13 +1,14 @@
 @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.action
+package net.mamoe.mirai.timpc.network.packet.action
 
 import io.ktor.util.date.GMTDate
 import kotlinx.io.core.*
-import net.mamoe.mirai.contact.data.Gender
-import net.mamoe.mirai.contact.data.Profile
+import net.mamoe.mirai.network.data.Gender
+import net.mamoe.mirai.network.data.Profile
 import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.protocol.timpc.packet.*
+import net.mamoe.mirai.network.data.Packet
+import net.mamoe.mirai.timpc.network.packet.*
 import net.mamoe.mirai.utils.io.*
 
 inline class AvatarLink(val value: String) : Packet
@@ -23,13 +24,13 @@ internal object QueryNicknamePacket : SessionPacketFactory<NicknameMap>() {
      */
     @PacketVersion(date = "2019.12.7", timVersion = "2.3.2 (21173)")
     operator fun invoke(
-        bot: UInt,
+        bot: Long,
         sessionKey: SessionKey,
-        qq: UInt
+        qq: Long
     ): OutgoingPacket = buildSessionPacket(bot, sessionKey) {
         writeHex("03 00 00 00 00 00 00 00 00 00 00")
         writeByte(1)
-        writeUInt(qq)
+        writeQQ(qq)
     }
 
     /**
@@ -38,7 +39,7 @@ internal object QueryNicknamePacket : SessionPacketFactory<NicknameMap>() {
      */
     @PacketVersion(date = "2019.12.7", timVersion = "2.3.2 (21173)")
     operator fun invoke(
-        bot: UInt,
+        bot: Long,
         sessionKey: SessionKey,
         qq: Array<UInt>
     ): OutgoingPacket = buildSessionPacket(bot, sessionKey) {
@@ -109,7 +110,7 @@ body=03 00 00 00 00 00 00 00 00 00 00 00 03 8E 3C A6 A0 EE EF 02 07 6C 01 14 E8
 04 5D EC AF 48
 */
 
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): NicknameMap {
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): NicknameMap {
         //03 00 00 00 00 00 00 00 00 00 00 12 04 14 37
         val type = readUByte().toInt()
         if (type == 15) {
@@ -137,16 +138,16 @@ body=03 00 00 00 00 00 00 00 00 00 00 00 03 8E 3C A6 A0 EE EF 02 07 6C 01 14 E8
 internal object RequestProfileAvatarPacket : SessionPacketFactory<AvatarLink>() {
     //00 01 00 17 D4 54 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 29 4E 22 4E 25 4E 26 4E 27 4E 29 4E 2A 4E 2B 4E 2D 4E 2E 4E 2F 4E 30 4E 31 4E 33 4E 35 4E 36 4E 37 4E 38 4E 3F 4E 40 4E 41 4E 42 4E 43 4E 45 4E 49 4E 4B 4E 4F 4E 54 4E 5B 52 0B 52 0F 5D C2 5D C8 65 97 69 9D 69 A9 9D A5 A4 91 A4 93 A4 94 A4 9C A4 B5
     operator fun invoke(
-        bot: UInt,
-        qq: UInt,
+        bot: Long,
+        qq: Long,
         sessionKey: SessionKey
     ): OutgoingPacket = buildSessionPacket(bot, sessionKey) {
         writeUShort(0x01u)
-        writeUInt(qq)
+        writeQQ(qq)
         writeHex("00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 29 4E 22 4E 25 4E 26 4E 27 4E 29 4E 2A 4E 2B 4E 2D 4E 2E 4E 2F 4E 30 4E 31 4E 33 4E 35 4E 36 4E 37 4E 38 4E 3F 4E 40 4E 41 4E 42 4E 43 4E 45 4E 49 4E 4B 4E 4F 4E 54 4E 5B 52 0B 52 0F 5D C2 5D C8 65 97 69 9D 69 A9 9D A5 A4 91 A4 93 A4 94 A4 9C A4 B5")
     }
 
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): AvatarLink {
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): AvatarLink {
         println("  RequestProfileAvatarPacket body=${this.readBytes().toUHexString()}")
         TODO()
     }
@@ -163,19 +164,19 @@ internal object RequestProfileDetailsPacket : SessionPacketFactory<RequestProfil
     //00 01 87 73 86 9D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 29 4E 22 4E 25 4E 26 4E 27 4E 29 4E 2A 4E 2B 4E 2D 4E 2E 4E 2F 4E 30 4E 31 4E 33 4E 35 4E 36 4E 37 4E 38 4E 3F 4E 40 4E 41 4E 42 4E 43 4E 45 4E 49 4E 4B 4E 4F 4E 54 4E 5B 52 0B 52 0F 5D C2 5D C8 65 97 69 9D 69 A9 9D A5 A4 91 A4 93 A4 94 A4 9C A4 B5
     @PacketVersion(date = "2019.11.2", timVersion = "2.3.2 (21173)")
     operator fun invoke(
-        bot: UInt,
-        qq: UInt,
+        bot: Long,
+        qq: Long,
         sessionKey: SessionKey
     ): OutgoingPacket = buildSessionPacket(bot, sessionKey) {
         writeUShort(0x01u)
-        writeUInt(qq)
+        writeQQ(qq)
         writeHex("00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 29 4E 22 4E 25 4E 26 4E 27 4E 29 4E 2A 4E 2B 4E 2D 4E 2E 4E 2F 4E 30 4E 31 4E 33 4E 35 4E 36 4E 37 4E 38 4E 3F 4E 40 4E 41 4E 42 4E 43 4E 45 4E 49 4E 4B 4E 4F 4E 54 4E 5B 52 0B 52 0F 5D C2 5D C8 65 97 69 9D 69 A9 9D A5 A4 91 A4 93 A4 94 A4 9C A4 B5")
     }
 
     @PacketVersion(date = "2019.11.2", timVersion = "2.3.2 (21173)")
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): RequestProfileDetailsResponse {
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): RequestProfileDetailsResponse {
         discardExact(3)
-        val qq = readUInt()
+        val qq = readUInt().toLong()
         discardExact(6)
         val map = readTLVMap(tagSize = 2, expectingEOF = true)
         //map.printTLVMap("Profile(qq=$qq) raw=")
@@ -193,7 +194,7 @@ internal object RequestProfileDetailsPacket : SessionPacketFactory<RequestProfil
                 0x02u -> Gender.FEMALE
                 0x01u -> Gender.MALE
                 else -> Gender.SECRET // 猜的
-                //else -> error("Cannot determine gender, bad value of 0x4E29u: ${map[0x4729u]!![0].toUHexString()}")
+                //else -> error("Cannot determine gender, bad value of 0x4E29u: ${map[0x4729u]!![0].toHexString()}")
             },
             birthday = map[0x4E3Fu]?.let { GMTDate(it.toUInt().toLong()) },
             personalStatement = map[0x4E33u]?.encodeToString(),
@@ -209,7 +210,7 @@ internal object RequestProfileDetailsPacket : SessionPacketFactory<RequestProfil
 }
 
 internal data class RequestProfileDetailsResponse(
-    val qq: UInt,
+    val qq: Long,
     val profile: Profile
 ) : Packet {
     //00 01 00 99 6B F8 D2 00 00 00 00 00 29

+ 6 - 10
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/Remark.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/Remark.kt

@@ -1,29 +1,25 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.action
+package net.mamoe.mirai.timpc.network.packet.action
 
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.discardExact
 import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.protocol.timpc.packet.*
+import net.mamoe.mirai.network.data.FriendNameRemark
+import net.mamoe.mirai.timpc.network.packet.*
 import net.mamoe.mirai.utils.io.readUShortLVString
 import net.mamoe.mirai.utils.io.writeQQ
 import net.mamoe.mirai.utils.io.writeZero
 
-/**
- * 给好友设置的备注
- */
-inline class FriendNameRemark(val value: String) : Packet
-
 internal object QueryFriendRemarkPacket : SessionPacketFactory<FriendNameRemark>() {
     /**
      * 查询好友的备注
      */
     @PacketVersion(date = "2019.11.27", timVersion = "2.3.2 (21173)")
     operator fun invoke(
-        bot: UInt,
+        bot: Long,
         sessionKey: SessionKey,
-        target: UInt
+        target: Long
     ): OutgoingPacket = buildSessionPacket(
         bot, sessionKey
     ) {
@@ -32,7 +28,7 @@ internal object QueryFriendRemarkPacket : SessionPacketFactory<FriendNameRemark>
         writeZero(1)
     }
 
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): FriendNameRemark {
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): FriendNameRemark {
         //0D 00 5D DA 3D 0F 59 17 3E 05 00 00 06 E6 9F 90 E4 B9 90 00 00 00 00 00 00
         discardExact(11)
         return FriendNameRemark(readUShortLVString())

+ 7 - 6
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/RequestFriendListPacket.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/RequestFriendListPacket.kt

@@ -1,19 +1,20 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.action
+package net.mamoe.mirai.timpc.network.packet.action
 
 import kotlinx.io.core.ByteReadPacket
 import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
-import net.mamoe.mirai.network.protocol.timpc.packet.*
+import net.mamoe.mirai.network.data.Packet
+import net.mamoe.mirai.timpc.network.TIMProtocol
+import net.mamoe.mirai.timpc.network.packet.*
 import net.mamoe.mirai.utils.io.writeZero
 
 class FriendList : Packet
 
-@PacketVersion(date = "2019.11.24", timVersion = "2.3.2 (21173)")
 internal object RequestFriendListPacket : SessionPacketFactory<FriendList>() {
+    @PacketVersion(date = "2019.11.24", timVersion = "2.3.2 (21173)")
     operator fun invoke(
-        bot: UInt,
+        bot: Long,
         sessionKey: SessionKey
     ): OutgoingPacket = buildSessionPacket(
         bot, sessionKey, version = TIMProtocol.version0x02
@@ -22,7 +23,7 @@ internal object RequestFriendListPacket : SessionPacketFactory<FriendList>() {
         writeZero(4)
     }
 
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): FriendList {
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): FriendList {
 
         TODO()
     }

+ 10 - 12
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/SendFriendMessagePacket.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/SendFriendMessagePacket.kt

@@ -1,21 +1,22 @@
 @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.action
+package net.mamoe.mirai.timpc.network.packet.action
 
 import kotlinx.io.core.*
-import net.mamoe.mirai.message.MessageChain
+import net.mamoe.mirai.message.data.MessageChain
 import net.mamoe.mirai.message.internal.toPacket
 import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
-import net.mamoe.mirai.network.protocol.timpc.packet.*
+import net.mamoe.mirai.network.data.Packet
+import net.mamoe.mirai.timpc.network.TIMProtocol
+import net.mamoe.mirai.timpc.network.packet.*
 import net.mamoe.mirai.utils.io.*
 import net.mamoe.mirai.utils.md5
 
 @PacketVersion(date = "2019.10.19", timVersion = "2.3.2 (21173)")
 internal object SendFriendMessagePacket : SessionPacketFactory<SendFriendMessagePacket.Response>() {
     operator fun invoke(
-        botQQ: UInt,
-        targetQQ: UInt,
+        botQQ: Long,
+        targetQQ: Long,
         sessionKey: SessionKey,
         message: MessageChain
     ): OutgoingPacket = buildSessionPacket(botQQ, sessionKey) {
@@ -25,14 +26,11 @@ internal object SendFriendMessagePacket : SessionPacketFactory<SendFriendMessage
         writeHex("38 03")
         writeQQ(botQQ)
         writeQQ(targetQQ)
-        writeFully(md5(buildPacket { writeQQ(targetQQ); writeFully(sessionKey) }.readBytes()))
+        writeFully(md5(buildPacket { writeQQ(targetQQ); writeFully(sessionKey.value) }.readBytes()))
         writeHex("00 0B")
         writeRandom(2)
         writeTime()
-        writeHex(
-            "01 1D" +
-                    " 00 00 00 00"
-        )
+        writeHex("01 1D 00 00 00 00")
 
         //消息过多要分包发送
         //如果只有一个
@@ -68,5 +66,5 @@ internal object SendFriendMessagePacket : SessionPacketFactory<SendFriendMessage
         override fun toString(): String = "SendFriendMessagePacket.Response"
     }
 
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): Response = Response
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): Response = Response
 }

+ 3 - 3
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/AndroidOnlineStatusChange.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/AndroidOnlineStatusChange.kt

@@ -1,12 +1,12 @@
 @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.event
+package net.mamoe.mirai.timpc.network.packet.event
 
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.discardExact
 import net.mamoe.mirai.Bot
-import net.mamoe.mirai.network.protocol.timpc.packet.Packet
-import net.mamoe.mirai.network.protocol.timpc.packet.PacketVersion
+import net.mamoe.mirai.network.data.Packet
+import net.mamoe.mirai.timpc.network.packet.PacketVersion
 import net.mamoe.mirai.utils.io.readBoolean
 
 

+ 2 - 8
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/ConnectionOccupiedEvent.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/ConnectionOccupiedEvent.kt

@@ -1,20 +1,14 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.event
+package net.mamoe.mirai.timpc.network.packet.event
 
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.discardExact
 import kotlinx.io.core.readBytes
 import net.mamoe.mirai.Bot
+import net.mamoe.mirai.event.events.ConnectionOccupiedEvent
 import net.mamoe.mirai.utils.io.encodeToString
 
-/**
- * 被挤下线. 只能获取到中文的消息
- */
-inline class ConnectionOccupiedEvent(val message: String) : EventPacket {
-    override fun toString(): String = "ConnectionOccupiedEvent(${message.replace("\n", "")})"
-}
-
 internal object ConnectionOccupiedPacketHandler : KnownEventParserAndHandler<ConnectionOccupiedEvent>(0x0030u) {
     override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): ConnectionOccupiedEvent {
         discardExact(6)

+ 13 - 17
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/EventPacketFactory.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/EventPacketFactory.kt

@@ -1,13 +1,13 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.event
+package net.mamoe.mirai.timpc.network.packet.event
 
 import kotlinx.io.core.*
 import net.mamoe.mirai.Bot
 import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.protocol.timpc.TIMBotNetworkHandler
-import net.mamoe.mirai.network.protocol.timpc.packet.*
-import net.mamoe.mirai.network.sessionKey
+import net.mamoe.mirai.network.data.Packet
+import net.mamoe.mirai.timpc.network.TIMBotNetworkHandler
+import net.mamoe.mirai.timpc.network.packet.*
 import net.mamoe.mirai.qqAccount
 import net.mamoe.mirai.utils.io.readIoBuffer
 
@@ -15,16 +15,16 @@ import net.mamoe.mirai.utils.io.readIoBuffer
  * 事件的识别 ID. 在 ACK 时使用
  */
 internal class EventPacketIdentity(
-    val from: UInt,//对于好友消息, 这个是发送人
-    val to: UInt,//对于好友消息, 这个是bot
+    val from: Long,//对于好友消息, 这个是发送人
+    val to: Long,//对于好友消息, 这个是bot
     internal val uniqueId: IoBuffer//8
 ) {
     override fun toString(): String = "($from->$to)"
 }
 
 internal fun BytePacketBuilder.writeEventPacketIdentity(identity: EventPacketIdentity) = with(identity) {
-    writeUInt(from)
-    writeUInt(to)
+    writeUInt(from.toUInt())
+    writeUInt(to.toUInt())
     writeFully(uniqueId)
 }
 
@@ -39,20 +39,16 @@ internal fun matchEventPacketFactory(value: UShort): EventParserAndHandler<*> =
 @NoLog
 @Suppress("FunctionName")
 internal object EventPacketFactory : PacketFactory<Packet, SessionKey>(SessionKey) {
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): Packet {
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): Packet {
         val eventIdentity = EventPacketIdentity(
-            from = readUInt(),
-            to = readUInt(),
+            from = readUInt().toLong(),  // clear semantic, don't readQQ() or readGroup()
+            to = readUInt().toLong(), // clear semantic
             uniqueId = readIoBuffer(8)
         )
         (handler as TIMBotNetworkHandler).socket.sendPacket(EventPacketFactory(id, sequenceId, handler.bot.qqAccount, handler.sessionKey, eventIdentity))
         discardExact(2) // 1F 40
 
         return with(matchEventPacketFactory(readUShort())) { parse(handler.bot, eventIdentity) }.also {
-            if (it is MessagePacket<*, *>) {
-                it.botVar = handler.bot
-            }
-
             if (it is EventParserAndHandler<*>) {
                 @Suppress("UNCHECKED_CAST")
                 with(it as EventParserAndHandler<in Packet>) {
@@ -68,7 +64,7 @@ internal object EventPacketFactory : PacketFactory<Packet, SessionKey>(SessionKe
     operator fun invoke(
         id: PacketId,
         sequenceId: UShort,
-        bot: UInt,
+        bot: Long,
         sessionKey: SessionKey,
         identity: EventPacketIdentity
     ): OutgoingPacket = buildSessionPacket(name = "EventPacket", id = id, sequenceId = sequenceId, bot = bot, sessionKey = sessionKey) {
@@ -84,7 +80,7 @@ internal interface EventParserAndHandler<TPacket : Packet> {
     /**
      * 在 [BotNetworkHandler] 下处理这个包. 广播事件等.
      */
-    suspend fun BotNetworkHandler<*>.handlePacket(packet: TPacket) {}
+    suspend fun BotNetworkHandler.handlePacket(packet: TPacket) {}
 }
 
 internal abstract class KnownEventParserAndHandler<TPacket : Packet>(override val id: UShort) : EventParserAndHandler<TPacket> {

+ 6 - 31
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/FriendAddRequestEventPacket.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/FriendAddRequestEventPacket.kt

@@ -1,44 +1,19 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.event
+package net.mamoe.mirai.timpc.network.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.QQ
-import net.mamoe.mirai.network.protocol.timpc.packet.PacketVersion
-import net.mamoe.mirai.network.protocol.timpc.packet.action.AddFriendPacket
-import net.mamoe.mirai.network.qqAccount
+import net.mamoe.mirai.event.events.ReceiveFriendAddRequestEvent
+import net.mamoe.mirai.timpc.network.packet.PacketVersion
+import net.mamoe.mirai.utils.io.readQQ
 import net.mamoe.mirai.utils.io.readUShortLVString
-import net.mamoe.mirai.withSession
-import kotlin.jvm.JvmOverloads
 
 
-/**
- * 陌生人请求添加机器人账号为好友
- */
-data class ReceiveFriendAddRequestEvent(
-    val qq: QQ,
-    /**
-     * 验证消息
-     */
-    val message: String
-) : EventPacket {
-    /**
-     * 同意这个请求
-     *
-     * @param remark 备注名, 不设置则需为 `null`
-     */
-    @JvmOverloads
-    suspend fun approve(remark: String? = null): Unit = qq.bot.withSession {
-        AddFriendPacket.Approve(qqAccount, sessionKey, 0, qq.id, remark).sendAndExpect<AddFriendPacket.Response>()
-    }
-}
-
 @PacketVersion(date = "2019.11.20", timVersion = "2.3.2 (21173)")
 internal object FriendAddRequestEventPacket : KnownEventParserAndHandler<ReceiveFriendAddRequestEvent>(0x02DFu) {
-    override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): ReceiveFriendAddRequestEvent = bot.withSession {
+    override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): ReceiveFriendAddRequestEvent = with(bot) {
         // 00 00 00 08 00 0A 00 04 01 00
         // 00 00 00 01
         // 76 E4 B8 DD
@@ -88,7 +63,7 @@ Mirai 20:35:23 : Packet received: UnknownEventPacket(id=00 BB, identity=(7610254
         discardExact(10 + 4) // 00 00 00 08 00 0A 00 04 01 00  00 00 00 01
         discardExact(4) // bot account uint
         discardExact(4) // 00 00 00 01
-        val qq = readUInt().qq()
+        val qq = readQQ().qq()
         discardExact(4) // bot  account uint
         discardExact(3) // 02 00 00 恒定
 

+ 6 - 4
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/FriendConversationIniliaze.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/FriendConversationIniliaze.kt

@@ -1,23 +1,25 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.event
+package net.mamoe.mirai.timpc.network.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.timpc.packet.PacketVersion
+import net.mamoe.mirai.network.data.EventPacket
+import net.mamoe.mirai.timpc.network.packet.PacketVersion
+import net.mamoe.mirai.utils.io.readQQ
 
 
 data class FriendConversationInitialize(
-    val qq: UInt
+    val qq: Long
 ) : EventPacket
 
 @PacketVersion(date = "2019.11.2", timVersion = "2.3.2 (21173)")
 internal object FriendConversationInitializedEventParserAndHandler : KnownEventParserAndHandler<FriendConversationInitialize>(0x0079u) {
     override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): FriendConversationInitialize {
         discardExact(4)// 00 00 00 00
-        return FriendConversationInitialize(readUInt())
+        return FriendConversationInitialize(readQQ())
     }
 
 }

+ 8 - 12
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/FriendOnlineStatusChanged.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/FriendOnlineStatusChanged.kt

@@ -1,30 +1,26 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "JoinDeclarationAndAssignment")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.event
+package net.mamoe.mirai.timpc.network.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.contact.QQ
+import net.mamoe.mirai.event.events.FriendStatusChanged
 import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.protocol.timpc.packet.KnownPacketId
-import net.mamoe.mirai.network.protocol.timpc.packet.PacketId
-import net.mamoe.mirai.network.protocol.timpc.packet.SessionPacketFactory
+import net.mamoe.mirai.timpc.network.packet.KnownPacketId
+import net.mamoe.mirai.timpc.network.packet.PacketId
+import net.mamoe.mirai.timpc.network.packet.SessionPacketFactory
 import net.mamoe.mirai.utils.OnlineStatus
-
-data class FriendStatusChanged(
-    val qq: QQ,
-    val status: OnlineStatus
-) : EventPacket
+import net.mamoe.mirai.utils.io.readQQ
 
 /**
  * 好友在线状态改变
  */
 internal object FriendOnlineStatusChangedPacket : SessionPacketFactory<FriendStatusChanged>() {
 
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): FriendStatusChanged {
-        val qq = readUInt()
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): FriendStatusChanged {
+        val qq = readQQ()
         discardExact(8)
         val statusId = readUByte()
         val status = OnlineStatus(statusId)

+ 3 - 2
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/GroupFileUpload.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/GroupFileUpload.kt

@@ -1,10 +1,11 @@
 @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.event
+package net.mamoe.mirai.timpc.network.packet.event
 
 import kotlinx.io.core.ByteReadPacket
 import net.mamoe.mirai.Bot
-import net.mamoe.mirai.network.protocol.timpc.packet.PacketVersion
+import net.mamoe.mirai.network.data.EventPacket
+import net.mamoe.mirai.timpc.network.packet.PacketVersion
 import net.mamoe.mirai.utils.io.debugPrint
 
 

+ 2 - 1
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/Ignored.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/Ignored.kt

@@ -1,9 +1,10 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.event
+package net.mamoe.mirai.timpc.network.packet.event
 
 import kotlinx.io.core.ByteReadPacket
 import net.mamoe.mirai.Bot
+import net.mamoe.mirai.network.data.EventPacket
 import net.mamoe.mirai.utils.io.toUHexString
 
 internal inline class IgnoredEventPacket(val id: UShort) : EventPacket {

+ 11 - 10
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/MemberJoin.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/MemberJoin.kt

@@ -1,21 +1,19 @@
 @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "unused", "MemberVisibilityCanBePrivate")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.event
+package net.mamoe.mirai.timpc.network.packet.event
 
 import kotlinx.io.core.ByteReadPacket
-import kotlinx.io.core.readUInt
 import net.mamoe.mirai.Bot
 import net.mamoe.mirai.contact.Group
 import net.mamoe.mirai.contact.Member
 import net.mamoe.mirai.contact.MemberPermission
-import net.mamoe.mirai.contact.internal.Member
-import net.mamoe.mirai.contact.internal.MemberImpl
 import net.mamoe.mirai.event.Subscribable
 import net.mamoe.mirai.event.broadcast
-import net.mamoe.mirai.getGroup
 import net.mamoe.mirai.network.BotNetworkHandler
+import net.mamoe.mirai.timpc.TIMPCBot
 import net.mamoe.mirai.utils.MiraiInternalAPI
 import net.mamoe.mirai.utils.io.discardExact
+import net.mamoe.mirai.utils.io.readQQ
 
 /**
  * 成员加入前的事件. 群的成员列表中还没有这个人
@@ -71,20 +69,23 @@ internal object MemberJoinPacketHandler : KnownEventParserAndHandler<MemberJoinE
         discardExact(11) //00 00 00 08 00 0A 00 04 01 00 00
 
         discardExact(1) // 00
-        val group = bot.getGroup(readUInt())
+        val group = bot.getGroup(readQQ())
 
         discardExact(1) // 01
-        val qq = bot.getQQ(readUInt())
-        val member = group.Member(qq, MemberPermission.MEMBER, qq.coroutineContext)
+        val qq = bot.getQQ(readQQ())
+        val member = with(bot) {
+            this as? TIMPCBot ?: error("wrong Bot type passed")
+            group.Member(qq, MemberPermission.MEMBER)
+        }
 
         return if (readByte().toInt() == 0x03) {
             MemberJoinEventPacket(member, null)
         } else {
-            MemberJoinEventPacket(member, group.getMember(readUInt()))
+            MemberJoinEventPacket(member, group.getMember(readQQ()))
         }
     }
 
-    override suspend fun BotNetworkHandler<*>.handlePacket(packet: MemberJoinEventPacket) {
+    override suspend fun BotNetworkHandler.handlePacket(packet: MemberJoinEventPacket) {
         PreMemberJoinEvent(packet).broadcast()
         packet.broadcast()
         PostMemberJoinEvent(packet).broadcast()

+ 9 - 13
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/MemberKickEvent.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/MemberKickEvent.kt

@@ -1,18 +1,15 @@
 @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.event
+package net.mamoe.mirai.timpc.network.packet.event
 
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.readUByte
-import kotlinx.io.core.readUInt
 import net.mamoe.mirai.Bot
 import net.mamoe.mirai.contact.Group
 import net.mamoe.mirai.contact.Member
-import net.mamoe.mirai.getGroup
+import net.mamoe.mirai.network.data.EventPacket
 import net.mamoe.mirai.qqAccount
-import net.mamoe.mirai.utils.io.discardExact
-import net.mamoe.mirai.utils.io.toUHexString
-import net.mamoe.mirai.utils.io.unsupported
+import net.mamoe.mirai.utils.io.*
 
 /**
  * 群成员列表变动事件.
@@ -53,21 +50,21 @@ internal object MemberGoneEventPacketHandler : KnownEventParserAndHandler<Member
         discardExact(11)
 
         discardExact(1)
-        val group = bot.getGroup(readUInt())
+        val group = bot.getGroup(readGroup())
 
         discardExact(1)
-        val id = readUInt()
+        val id = readQQ()
         if (id == bot.qqAccount) {
             discardExact(1)
-            return BeingKickEvent(group, group.getMember(readUInt()))
+            return BeingKickEvent(group, group.getMember(readQQ()))
         }
 
         val member = group.getMember(id)
 
         return when (val type = readUByte().toInt()) {
             0x02 -> MemberQuitEvent(member, _operator = null)
-            0x03 -> MemberQuitEvent(member, _operator = group.getMember(readUInt()))
-            else -> unsupported("Unsupported type " + type.toUHexString())
+            0x03 -> MemberQuitEvent(member, _operator = group.getMember(readQQ()))
+            else -> error("Unsupported type " + type.toUHexString())
         }
 
         // 某群员主动离开, 群号 853343432
@@ -94,5 +91,4 @@ internal object MemberGoneEventPacketHandler : KnownEventParserAndHandler<Member
         // 00 30 32 33 32 63 32 39 36 65 36 35 64 62 64 64 64 64 65 35 62 33 34 64 36 62 34 33 32 61 30 64 61 65 32 30 37 35 38 34 37 34 32 65 32 39 63 35 63 64
 
     }
-}
-
+}

+ 6 - 73
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/MemberMute.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/MemberMute.kt

@@ -1,6 +1,6 @@
 @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "MemberVisibilityCanBePrivate")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.event
+package net.mamoe.mirai.timpc.network.packet.event
 
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.discardExact
@@ -9,75 +9,13 @@ import kotlinx.io.core.readUInt
 import net.mamoe.mirai.Bot
 import net.mamoe.mirai.contact.Group
 import net.mamoe.mirai.contact.Member
-import net.mamoe.mirai.getGroup
+import net.mamoe.mirai.event.events.*
 import net.mamoe.mirai.qqAccount
 import net.mamoe.mirai.utils.io.debugPrintIfFail
+import net.mamoe.mirai.utils.io.readQQ
 import net.mamoe.mirai.utils.io.readRemainingBytes
 import net.mamoe.mirai.utils.io.toUHexString
 
-// region mute
-/**
- * 某群成员被禁言事件
- */
-@Suppress("unused", "MemberVisibilityCanBePrivate")
-class MemberMuteEvent(
-    val member: Member,
-    override val durationSeconds: Int,
-    override val operator: Member
-) : MuteEvent() {
-    override val group: Group get() = operator.group
-    override fun toString(): String = "MemberMuteEvent(member=${member.id}, group=${group.id}, operator=${operator.id}, duration=${durationSeconds}s"
-}
-
-/**
- * 机器人被禁言事件
- */
-class BeingMutedEvent(
-    override val durationSeconds: Int,
-    override val operator: Member
-) : MuteEvent() {
-    override val group: Group get() = operator.group
-    override fun toString(): String = "BeingMutedEvent(group=${group.id}, operator=${operator.id}, duration=${durationSeconds}s"
-}
-
-sealed class MuteEvent : EventOfMute() {
-    abstract override val operator: Member
-    abstract override val group: Group
-    abstract val durationSeconds: Int
-}
-// endregion
-
-// region unmute
-/**
- * 某群成员被解除禁言事件
- */
-@Suppress("unused")
-class MemberUnmuteEvent(
-    val member: Member,
-    override val operator: Member
-) : UnmuteEvent() {
-    override val group: Group get() = operator.group
-    override fun toString(): String = "MemberUnmuteEvent(member=${member.id}, group=${group.id}, operator=${operator.id}"
-}
-
-/**
- * 机器人被解除禁言事件
- */
-@Suppress("SpellCheckingInspection")
-class BeingUnmutedEvent(
-    override val operator: Member
-) : UnmuteEvent() {
-    override val group: Group get() = operator.group
-    override fun toString(): String = "BeingUnmutedEvent(group=${group.id}, operator=${operator.id}"
-}
-
-sealed class UnmuteEvent : EventOfMute() {
-    abstract override val operator: Member
-    abstract override val group: Group
-}
-
-// endregion
-
 internal class Unknown0x02DCPacketFlag0x0EMaybeMutePacket(
     val remaining: ByteArray
 ) : EventOfMute() {
@@ -86,11 +24,6 @@ internal class Unknown0x02DCPacketFlag0x0EMaybeMutePacket(
     override fun toString(): String = "Unknown0x02DCPacketFlag0x0EMaybeMutePacket(remaining=${remaining.toUHexString()})"
 }
 
-sealed class EventOfMute : EventPacket {
-    abstract val operator: Member
-    abstract val group: Group
-}
-
 // TODO: 2019/12/14 这可能不只是禁言的包.
 internal object MemberMuteEventPacketParserAndHandler : KnownEventParserAndHandler<EventOfMute>(0x02DCu) {
     override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): EventOfMute {
@@ -133,12 +66,12 @@ internal object MemberMuteEventPacketParserAndHandler : KnownEventParserAndHandl
             0x11u -> debugPrintIfFail("解析禁言包(0x02DC)时"){ // 猜测这个失败是撤回??
                 discardExact(15)
                 discardExact(2)
-                val group = bot.getGroup(readUInt())
+                val group = bot.getGroup(readQQ())
                 discardExact(2)
-                val operator = group.getMember(readUInt())
+                val operator = group.getMember(readQQ())
                 discardExact(4) //time
                 discardExact(2)
-                val memberQQ = readUInt()
+                val memberQQ = readQQ()
 
                 val durationSeconds = readUInt().toInt()
                 if (durationSeconds == 0) {

+ 7 - 7
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/MemberPermission.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/MemberPermission.kt

@@ -1,18 +1,18 @@
 @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.event
+package net.mamoe.mirai.timpc.network.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.timpc.packet.Packet
-import net.mamoe.mirai.network.protocol.timpc.packet.PacketVersion
+import net.mamoe.mirai.network.data.Packet
+import net.mamoe.mirai.timpc.network.packet.PacketVersion
+import net.mamoe.mirai.utils.io.readQQ
 
 
 data class MemberPermissionChangePacket(
-    val groupId: UInt,
-    val qq: UInt,
+    val groupId: Long,
+    val qq: Long,
     val kind: Kind
 ) : Packet {
     enum class Kind {
@@ -35,7 +35,7 @@ internal object GroupMemberPermissionChangedEventFactory : KnownEventParserAndHa
         // 取消管理员
         // 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 qq = readQQ()
         val kind = when (readByte().toInt()) {
             0x00 -> MemberPermissionChangePacket.Kind.NO_LONGER_OPERATOR
             0x01 -> MemberPermissionChangePacket.Kind.BECOME_OPERATOR

+ 103 - 0
mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/MessageEvent.kt

@@ -0,0 +1,103 @@
+package net.mamoe.mirai.timpc.network.packet.event
+
+import kotlinx.io.core.ByteReadPacket
+import kotlinx.io.core.discardExact
+import net.mamoe.mirai.Bot
+import net.mamoe.mirai.contact.MemberPermission
+import net.mamoe.mirai.message.GroupMessage
+import net.mamoe.mirai.message.internal.readMessageChain
+import net.mamoe.mirai.message.FriendMessage
+import net.mamoe.mirai.timpc.network.packet.PacketVersion
+import net.mamoe.mirai.utils.MiraiLogger
+import net.mamoe.mirai.utils.io.*
+
+
+@UseExperimental(ExperimentalUnsignedTypes::class)
+internal object GroupMessageEventParserAndHandler : KnownEventParserAndHandler<GroupMessage>(0x0052u) {
+
+    @PacketVersion(date = "2019.11.2", timVersion = "2.3.2 (21173)")
+    override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): GroupMessage {
+        discardExact(31)
+        val groupNumber = readGroup()
+        discardExact(1)
+        val qq = readQQ()
+
+        discardExact(48)
+        readUShortLVByteArray()
+        discardExact(2)//2个0x00
+
+        //debugPrintIfFail {
+        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.ADMINISTRATOR
+                    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"
+                    }
+                }
+            }
+        }
+
+        val group = bot.getGroup(groupNumber)
+        return GroupMessage(
+            bot = bot,
+            group = group,
+            senderName = senderName,
+            permission = senderPermission,
+            sender = group.getMember(qq),
+            message = message
+        )
+    }
+}
+
+// endregion
+
+// region friend message
+
+
+@Suppress("unused")
+@UseExperimental(ExperimentalUnsignedTypes::class)
+internal object FriendMessageEventParserAndHandler : KnownEventParserAndHandler<FriendMessage>(0x00A6u) {
+
+    @PacketVersion(date = "2019.11.2", timVersion = "2.3.2 (21173)")
+    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(
+            bot = bot,
+            previous = previous,
+            sender = bot.getQQ(identity.from),
+            message = message
+        )
+    }
+}
+// endregion

+ 4 - 3
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/Unknown.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/Unknown.kt

@@ -1,12 +1,13 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.event
+package net.mamoe.mirai.timpc.network.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.network.data.EventPacket
 import net.mamoe.mirai.utils.io.ByteArrayPool
 import net.mamoe.mirai.utils.io.toUHexString
 
@@ -28,11 +29,11 @@ Mirai 21:54:15 : Packet received: UnknownEventPacket(id=00 57, identity=(9205034
 internal class UnknownEventParserAndHandler(override val id: UShort) : EventParserAndHandler<UnknownEventPacket> {
 
     override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): UnknownEventPacket {
-        // MiraiLogger.debug("UnknownEventPacket(${id.toUHexString()}) = ${readBytes().toUHexString()}")
+        // MiraiLogger.debug("UnknownEventPacket(${id.toHexString()}) = ${readBytes().toHexString()}")
         return UnknownEventPacket(id, identity, this) //TODO the cause is that `this` reference.
     }
 
-    override suspend fun BotNetworkHandler<*>.handlePacket(packet: UnknownEventPacket) {
+    override suspend fun BotNetworkHandler.handlePacket(packet: UnknownEventPacket) {
         ByteArrayPool.useInstance {
             packet.body.readAvailable(it)
             bot.logger.debug("Unknown packet(${packet.id}) data = " + it.toUHexString())

+ 8 - 7
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/Captcha.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/Captcha.kt

@@ -1,11 +1,12 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "unused", "FunctionName")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.login
+package net.mamoe.mirai.timpc.network.packet.login
 
 import kotlinx.io.core.*
 import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
-import net.mamoe.mirai.network.protocol.timpc.packet.*
+import net.mamoe.mirai.network.data.Packet
+import net.mamoe.mirai.timpc.network.TIMProtocol
+import net.mamoe.mirai.timpc.network.packet.*
 import net.mamoe.mirai.utils.io.*
 
 internal object CaptchaKey : DecrypterByteArray, DecrypterType<CaptchaKey> {
@@ -17,7 +18,7 @@ internal object CaptchaPacket : PacketFactory<CaptchaPacket.CaptchaResponse, Cap
      * 请求验证码传输
      */
     fun RequestTransmission(
-        bot: UInt,
+        bot: Long,
         token0825: ByteArray,
         captchaSequence: Int,
         token00BA: ByteArray
@@ -45,7 +46,7 @@ internal object CaptchaPacket : PacketFactory<CaptchaPacket.CaptchaResponse, Cap
      * 刷新验证码
      */
     fun Refresh(
-        bot: UInt,
+        bot: Long,
         token0825: ByteArray
     ): OutgoingPacket = buildOutgoingPacket(name = "CaptchaPacket.Refresh") {
         writeQQ(bot)
@@ -67,7 +68,7 @@ internal object CaptchaPacket : PacketFactory<CaptchaPacket.CaptchaResponse, Cap
      * 提交验证码
      */
     fun Submit(
-        bot: UInt,
+        bot: Long,
         token0825: ByteArray,
         captcha: String,
         captchaToken: IoBuffer
@@ -125,7 +126,7 @@ internal object CaptchaPacket : PacketFactory<CaptchaPacket.CaptchaResponse, Cap
         }
     }
 
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): CaptchaResponse =
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): CaptchaResponse =
         when (val flag = readByte().toUInt()) {
             0x14u -> {//00 05 00 00 00 00 00 00 38
                 CaptchaResponse.Correct().apply {

+ 7 - 7
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/ChangeOnlineStatusPacket.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/ChangeOnlineStatusPacket.kt

@@ -1,15 +1,15 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.login
+package net.mamoe.mirai.timpc.network.packet.login
 
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.writeFully
 import kotlinx.io.core.writeUByte
 import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
-import net.mamoe.mirai.network.protocol.timpc.packet.*
+import net.mamoe.mirai.network.data.Packet
+import net.mamoe.mirai.timpc.network.TIMProtocol
+import net.mamoe.mirai.timpc.network.packet.*
 import net.mamoe.mirai.utils.OnlineStatus
-import net.mamoe.mirai.utils.io.encryptAndWrite
 import net.mamoe.mirai.utils.io.writeHex
 import net.mamoe.mirai.utils.io.writeQQ
 
@@ -18,7 +18,7 @@ import net.mamoe.mirai.utils.io.writeQQ
  */
 internal object ChangeOnlineStatusPacket : PacketFactory<ChangeOnlineStatusPacket.ChangeOnlineStatusResponse, NoDecrypter>(NoDecrypter) {
     operator fun invoke(
-        bot: UInt,
+        bot: Long,
         sessionKey: SessionKey,
         loginStatus: OnlineStatus
     ): OutgoingPacket = buildOutgoingPacket {
@@ -31,10 +31,10 @@ internal object ChangeOnlineStatusPacket : PacketFactory<ChangeOnlineStatusPacke
         }
     }
 
-    internal   object ChangeOnlineStatusResponse : Packet {
+    internal object ChangeOnlineStatusResponse : Packet {
         override fun toString(): String = this::class.simpleName!!
     }
 
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): ChangeOnlineStatusResponse =
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): ChangeOnlineStatusResponse =
         ChangeOnlineStatusResponse
 }

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

@@ -1,21 +1,21 @@
 @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.login
+package net.mamoe.mirai.timpc.network.packet.login
 
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.writeFully
 import net.mamoe.mirai.event.Subscribable
 import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
-import net.mamoe.mirai.network.protocol.timpc.packet.*
-import net.mamoe.mirai.utils.io.encryptAndWrite
+import net.mamoe.mirai.network.data.Packet
+import net.mamoe.mirai.timpc.network.TIMProtocol
+import net.mamoe.mirai.timpc.network.packet.*
 import net.mamoe.mirai.utils.io.writeHex
 import net.mamoe.mirai.utils.io.writeQQ
 
 @NoLog
 internal object HeartbeatPacket : SessionPacketFactory<HeartbeatPacketResponse>() {
     operator fun invoke(
-        bot: UInt,
+        bot: Long,
         sessionKey: SessionKey
     ): OutgoingPacket = buildOutgoingPacket {
         writeQQ(bot)
@@ -25,7 +25,7 @@ internal object HeartbeatPacket : SessionPacketFactory<HeartbeatPacketResponse>(
         }
     }
 
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): HeartbeatPacketResponse =
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): HeartbeatPacketResponse =
         HeartbeatPacketResponse
 }
 

+ 34 - 12
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/PasswordSubmission.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/PasswordSubmission.kt

@@ -1,17 +1,16 @@
 @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.login
+package net.mamoe.mirai.timpc.network.packet.login
 
 import kotlinx.io.core.*
-import net.mamoe.mirai.contact.data.Gender
+import net.mamoe.mirai.network.data.Gender
 import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
-import net.mamoe.mirai.network.protocol.timpc.packet.*
-import net.mamoe.mirai.utils.MiraiLogger
-import net.mamoe.mirai.utils.decryptBy
-import net.mamoe.mirai.utils.encryptBy
+import net.mamoe.mirai.network.data.LoginResult
+import net.mamoe.mirai.network.data.Packet
+import net.mamoe.mirai.timpc.network.TIMProtocol
+import net.mamoe.mirai.timpc.network.packet.*
+import net.mamoe.mirai.utils.*
 import net.mamoe.mirai.utils.io.*
-import net.mamoe.mirai.utils.writeCRC32
 
 internal object ShareKey : DecrypterByteArray, DecrypterType<ShareKey> {
     override val value: ByteArray = TIMProtocol.shareKey
@@ -46,7 +45,7 @@ internal inline class SubmitPasswordResponseDecrypter(private val privateKey: Pr
  */
 internal object SubmitPasswordPacket : PacketFactory<SubmitPasswordPacket.LoginResponse, SubmitPasswordResponseDecrypter>(SubmitPasswordResponseDecrypter) {
     operator fun invoke(
-        bot: UInt,
+        bot: Long,
         password: String,
         loginTime: Int,
         loginIP: String,
@@ -109,7 +108,7 @@ internal object SubmitPasswordPacket : PacketFactory<SubmitPasswordPacket.LoginR
         data class Failed(val result: LoginResult) : LoginResponse()
     }
 
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): 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 || size == 165 /* TODO CHECK 165 */ -> {
@@ -267,7 +266,7 @@ internal inline class SessionResponseDecryptionKey(private val delegate: IoBuffe
 }
 
 private fun BytePacketBuilder.writePart1(
-    qq: UInt,
+    qq: Long,
     password: String,
     loginTime: Int,
     loginIP: String,
@@ -296,7 +295,7 @@ private fun BytePacketBuilder.writePart1(
     this.writeFully(TIMProtocol.passwordSubmissionTLV2)
     this.writeHex("00 1A")//tag
     this.writeHex("00 40")//length
-    this.writeFully(TIMProtocol.passwordSubmissionTLV2.encryptBy(privateKey))
+    this.writeFully(TIMProtocol.passwordSubmissionTLV2.encryptBy(privateKey.value))
     this.writeFully(TIMProtocol.constantData1)
     this.writeFully(TIMProtocol.constantData2)
     this.writeQQ(qq)
@@ -310,6 +309,29 @@ private fun BytePacketBuilder.writePart1(
     this.writeHex("60 C9 5D A7 45 70 04 7F 21 7D 84 50 5C 66 A5 C6")//key
 }
 
+private fun BytePacketBuilder.writeTLV0006(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: PrivateKey) {
+    val firstMD5 = md5(password)
+    val secondMD5 = md5(firstMD5 + byteArrayOf(0, 0, 0, 0) + qq.toUInt().toByteArray())
+
+    this.encryptAndWrite(secondMD5) {
+        writeRandom(4)
+        writeHex("00 02")
+        writeQQ(qq)
+        writeFully(TIMProtocol.constantData2)
+        writeHex("00 00 01")
+
+        writeFully(firstMD5)
+        writeInt(loginTime)
+        writeByte(0)
+        writeZero(4 * 3)
+        writeIP(loginIP)
+        writeZero(8)
+        writeHex("00 10")//这两个hex是passwordSubmissionTLV2的末尾
+        writeHex("15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B")//16
+        writeFully(privateKey.value)
+    }
+}
+
 private fun BytePacketBuilder.writePart2() {
 
     this.writeHex("03 12")//tag

+ 9 - 13
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/SKey.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/SKey.kt

@@ -1,19 +1,15 @@
 @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "FunctionName")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.login
+package net.mamoe.mirai.timpc.network.packet.login
 
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.discardExact
 import kotlinx.io.core.writeFully
 import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.BotSession
-import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
-import net.mamoe.mirai.network.protocol.timpc.packet.*
-import net.mamoe.mirai.network.qqAccount
+import net.mamoe.mirai.network.data.Packet
+import net.mamoe.mirai.timpc.network.TIMProtocol
+import net.mamoe.mirai.timpc.network.packet.*
 import net.mamoe.mirai.utils.io.*
-import net.mamoe.mirai.withSession
-
-internal fun BotSession.RequestSKeyPacket(): OutgoingPacket = RequestSKeyPacket(qqAccount, sessionKey)
 
 internal inline class SKey(
     val value: String
@@ -25,7 +21,7 @@ internal inline class SKey(
  */
 internal object RequestSKeyPacket : SessionPacketFactory<SKey>() {
     operator fun invoke(
-        bot: UInt,
+        bot: Long,
         sessionKey: SessionKey
     ): OutgoingPacket = buildOutgoingPacket {
         writeQQ(bot)
@@ -35,7 +31,7 @@ internal object RequestSKeyPacket : SessionPacketFactory<SKey>() {
         }
     }
 
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): SKey {
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): SKey {
         //11 00 97 D7 0F 1C FD 50 7A 41 DD 4D 66 93 EF 8C 85 D1 84 3D 66 95 9D E5 B4 96 A5 E3 92 37 28 D8 80 DA EF 8C 85 D1 84 3D 66 95 9D E5 B4 96 A5 E3 92 37 28 D8 80 DA
 
         discardExact(4)
@@ -44,9 +40,9 @@ internal object RequestSKeyPacket : SessionPacketFactory<SKey>() {
         }
     }
 
-    override suspend fun BotNetworkHandler<*>.handlePacket(packet: SKey) = bot.withSession {
-        _sKey = packet.value
-        _cookies = "uin=o$qqAccount;skey=$sKey;"
+    override suspend fun BotNetworkHandler.handlePacket(packet: SKey) {
+        // _sKey = packet.value
+        // _cookies = "uin=o$qqAccount;skey=$sKey;"
 
         // TODO: 2019/11/27 SKEY 实现
         /*

+ 6 - 5
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/Session.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/Session.kt

@@ -1,17 +1,18 @@
 @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.login
+package net.mamoe.mirai.timpc.network.packet.login
 
 import kotlinx.io.core.*
 import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
-import net.mamoe.mirai.network.protocol.timpc.packet.*
+import net.mamoe.mirai.network.data.Packet
+import net.mamoe.mirai.timpc.network.TIMProtocol
+import net.mamoe.mirai.timpc.network.packet.*
 import net.mamoe.mirai.utils.io.*
 import net.mamoe.mirai.utils.localIpAddress
 
 internal object RequestSessionPacket : PacketFactory<RequestSessionPacket.SessionKeyResponse, SessionResponseDecryptionKey>(SessionResponseDecryptionKey) {
     operator fun invoke(
-        bot: UInt,
+        bot: Long,
         serverIp: String,
         token38: IoBuffer,
         token88: IoBuffer,
@@ -65,7 +66,7 @@ internal object RequestSessionPacket : PacketFactory<RequestSessionPacket.Sessio
         override fun toString(): String = "SessionKeyResponse"
     }
 
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): SessionKeyResponse {
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): SessionKeyResponse {
         when (remaining) {
             407L -> {
                 discardExact(25)//todo test

+ 6 - 5
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/Touch.kt → mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/Touch.kt

@@ -1,14 +1,15 @@
 @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
 
-package net.mamoe.mirai.network.protocol.timpc.packet.login
+package net.mamoe.mirai.timpc.network.packet.login
 
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.discardExact
 import kotlinx.io.core.readBytes
 import kotlinx.io.core.writeFully
 import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
-import net.mamoe.mirai.network.protocol.timpc.packet.*
+import net.mamoe.mirai.network.data.Packet
+import net.mamoe.mirai.timpc.network.TIMProtocol
+import net.mamoe.mirai.timpc.network.packet.*
 import net.mamoe.mirai.utils.io.*
 
 internal object TouchKey : DecrypterByteArray, DecrypterType<TouchKey> {
@@ -22,7 +23,7 @@ internal object TouchKey : DecrypterByteArray, DecrypterType<TouchKey> {
  */
 internal object TouchPacket : PacketFactory<TouchPacket.TouchResponse, TouchKey>(TouchKey) {
     operator fun invoke(
-        bot: UInt,
+        bot: Long,
         serverIp: String,
         isRedirect: Boolean
     ): OutgoingPacket = buildOutgoingPacket {
@@ -60,7 +61,7 @@ internal object TouchPacket : PacketFactory<TouchPacket.TouchResponse, TouchKey>
         }
     }
 
-    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): TouchResponse {
+    override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): TouchResponse {
         when (val flag = readByte().toUByte().toInt()) {
             0xFE -> {
                 discardExact(94)

+ 7 - 0
mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/utils/Unreachable.kt

@@ -0,0 +1,7 @@
+package net.mamoe.mirai.timpc.utils
+
+/**
+ * 表示这里是不可到达的位置.
+ */
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun assertUnreachable(): Nothing = error("This clause should not be reached")

+ 11 - 0
mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/utils/unsupportedFlag.kt

@@ -0,0 +1,11 @@
+package net.mamoe.mirai.timpc.utils
+
+import kotlinx.io.core.ByteReadPacket
+import kotlinx.io.core.readBytes
+import net.mamoe.mirai.utils.io.toUHexString
+
+internal fun ByteReadPacket.unsupportedFlag(name: String, flag: String): Nothing =
+    error("Unsupported flag of $name. flag=$flag, remaining=${readBytes().toUHexString()}")
+
+internal fun ByteReadPacket.unsupportedType(name: String, type: String): Nothing =
+    error("Unsupported type of $name. type=$type, remaining=${readBytes().toUHexString()}")

+ 30 - 0
mirai-core-timpc/src/jvmMain/kotlin/net/mamoe/mirai/timpc/TIMPCBot.kt

@@ -0,0 +1,30 @@
+@file:Suppress("unused")
+
+package net.mamoe.mirai.timpc
+
+import kotlinx.coroutines.Dispatchers.IO
+import kotlinx.coroutines.withContext
+import kotlinx.io.InputStream
+import kotlinx.io.core.use
+import kotlinx.io.streams.inputStream
+import net.mamoe.mirai.BotAccount
+import net.mamoe.mirai.message.data.Image
+import net.mamoe.mirai.utils.ExternalImage
+import net.mamoe.mirai.utils.MiraiLogger
+import net.mamoe.mirai.utils.toExternalImage
+import java.awt.image.BufferedImage
+import java.io.File
+import javax.imageio.ImageIO
+import kotlin.coroutines.CoroutineContext
+
+internal actual class TIMPCBot actual constructor(
+    account: BotAccount,
+    logger: MiraiLogger?,
+    context: CoroutineContext
+) : TIMPCBotBase(account, logger, context) {
+    suspend inline fun Image.downloadAsStream(): InputStream = download().inputStream()
+    suspend inline fun Image.downloadAsBufferedImage(): BufferedImage = withContext(IO) { downloadAsStream().use { ImageIO.read(it) } }
+    suspend inline fun Image.downloadAsExternalImage(): ExternalImage = download().use { it.toExternalImage() }
+
+    suspend inline fun Image.downloadTo(file: File) = file.outputStream().use { downloadTo(it) }
+}

+ 13 - 0
mirai-core-timpc/src/jvmMain/kotlin/net/mamoe/mirai/timpc/network/NetworkDispatcher.kt

@@ -0,0 +1,13 @@
+package net.mamoe.mirai.timpc.network
+
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.asCoroutineDispatcher
+import java.util.concurrent.Executors
+
+/**
+ * 包处理协程调度器.
+ *
+ * JVM: 独立的 4 thread 调度器
+ */
+internal actual val NetworkDispatcher: CoroutineDispatcher
+    get() = Executors.newFixedThreadPool(4).asCoroutineDispatcher()

+ 4 - 6
mirai-core/src/jvmTest/kotlin/mirai/test/BadQQFilter.kt → mirai-core-timpc/src/jvmTest/kotlin/BadQQFilter.kt

@@ -1,14 +1,12 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE")
 
-package mirai.test
-
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withContext
 import net.mamoe.mirai.Bot
-import net.mamoe.mirai.login
-import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult
+import net.mamoe.mirai.network.data.LoginResult
+import net.mamoe.mirai.timpc.TIMPC
 import java.util.*
 
 /**
@@ -44,8 +42,8 @@ suspend fun main() {
             .map { Pair(it[0].toLong(), it[1]) }
             .forEach { (qq, password) ->
                 runBlocking {
-                    val bot = Bot(
-                        qq.toUInt(),
+                    val bot = TIMPC.Bot(
+                        qq,
                         if (password.endsWith(".")) password.substring(0, password.length - 1) else password
                     )
 

+ 21 - 38
mirai-core/src/jvmTest/kotlin/mirai/test/packetdebugger/PacketDebugger.kt → mirai-core-timpc/src/jvmTest/kotlin/PacketDebugger.kt

@@ -1,29 +1,28 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE", "MemberVisibilityCanBePrivate", "EXPERIMENTAL_UNSIGNED_LITERALS")
 
-package mirai.test.packetdebugger
-
+import PacketDebugger.dataReceived
+import PacketDebugger.dataSent
+import PacketDebugger.qq
+import PacketDebugger.sessionKey
 import io.ktor.util.date.GMTDate
 import kotlinx.coroutines.*
-import kotlinx.io.core.*
+import kotlinx.io.core.ByteReadPacket
+import kotlinx.io.core.discardExact
+import kotlinx.io.core.readBytes
+import kotlinx.io.core.readUShort
 import kotlinx.serialization.ImplicitReflectionSerializer
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.internal.ArrayListSerializer
 import kotlinx.serialization.json.Json
-import mirai.test.packetdebugger.PacketDebugger.dataReceived
-import mirai.test.packetdebugger.PacketDebugger.dataSent
-import mirai.test.packetdebugger.PacketDebugger.qq
-import mirai.test.packetdebugger.PacketDebugger.sessionKey
 import net.mamoe.mirai.Bot
 import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.BotSession
-import net.mamoe.mirai.network.protocol.timpc.TIMProtocol
-import net.mamoe.mirai.network.protocol.timpc.handler.DataPacketSocketAdapter
-import net.mamoe.mirai.network.protocol.timpc.packet.*
-import net.mamoe.mirai.network.protocol.timpc.packet.event.IgnoredEventPacket
-import net.mamoe.mirai.network.protocol.timpc.packet.login.CaptchaKey
-import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult
-import net.mamoe.mirai.network.protocol.timpc.packet.login.ShareKey
-import net.mamoe.mirai.network.protocol.timpc.packet.login.TouchKey
+import net.mamoe.mirai.timpc.TIMPC
+import net.mamoe.mirai.timpc.network.TIMProtocol
+import net.mamoe.mirai.timpc.network.packet.*
+import net.mamoe.mirai.timpc.network.packet.event.IgnoredEventPacket
+import net.mamoe.mirai.timpc.network.packet.login.CaptchaKey
+import net.mamoe.mirai.timpc.network.packet.login.ShareKey
+import net.mamoe.mirai.timpc.network.packet.login.TouchKey
 import net.mamoe.mirai.utils.DecryptionFailedException
 import net.mamoe.mirai.utils.decryptBy
 import net.mamoe.mirai.utils.io.*
@@ -37,7 +36,6 @@ import java.nio.charset.Charset
 import java.util.concurrent.Executors
 import kotlin.concurrent.thread
 import kotlin.coroutines.CoroutineContext
-import kotlin.io.use
 
 /**
  * 避免 print 重叠. 单线程处理足够调试
@@ -189,7 +187,7 @@ internal object PacketDebugger {
     /**
      * null 则不筛选
      */
-    val qq: UInt? = 761025446u
+    val qq: Long? = 761025446
     /**
      * 打开后则记录每一个包到文件.
      */
@@ -208,7 +206,7 @@ internal object PacketDebugger {
             discardExact(3)
             val id = matchPacketId(readUShort())
             val sequenceId = readUShort()
-            val packetQQ = readUInt()
+            val packetQQ = readQQ()
             if (id == KnownPacketId.HEARTBEAT || (qq != null && packetQQ != qq))
                 return@read
 
@@ -308,7 +306,7 @@ internal object PacketDebugger {
         if (IgnoredPacketIdList.contains(id)) {
             return
         }
-        val packetQQ = readUInt()
+        val packetQQ = readQQ()
         if (qq != null && packetQQ != qq) {
             return@read
         }
@@ -378,27 +376,12 @@ when (idHex.substring(0, 5)) {
 }
 
 
-internal object DebugNetworkHandler : BotNetworkHandler<DataPacketSocketAdapter>, CoroutineScope {
+internal object DebugNetworkHandler : BotNetworkHandler(), CoroutineScope {
     override val supervisor: CompletableJob = SupervisorJob()
-    override val socket: DataPacketSocketAdapter = object : DataPacketSocketAdapter {
-        override val serverIp: String
-            get() = ""
-        override val channel: PlatformDatagramChannel
-            get() = error("UNSUPPORTED")
-        override val isOpen: Boolean
-            get() = true
-
-        override fun close() {
-        }
 
-        override val owner: Bot
-            get() = bot
-
-    }
-    override val bot: Bot = Bot(qq ?: 0u, "", coroutineContext)
-    override val session = BotSession(bot, SessionKey(byteArrayOf()))
+    override val bot: Bot = TIMPC.run { [email protected](qq ?: 0L, "", null) }
 
-    override suspend fun login(): LoginResult = LoginResult.SUCCESS
+    override suspend fun login() {}
 
     override suspend fun awaitDisconnection() {
     }

+ 3 - 4
mirai-core/src/jvmTest/kotlin/mirai/test/packetdebugger/PacketDecoder.kt → mirai-core-timpc/src/jvmTest/kotlin/packetdebugger/PacketDecoder.kt

@@ -1,11 +1,10 @@
-package mirai.test.packetdebugger
+package packetdebugger
 
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.discardExact
 import kotlinx.io.core.readUShort
-import net.mamoe.mirai.network.protocol.timpc.packet.PacketId
-import net.mamoe.mirai.network.protocol.timpc.packet.login.CaptchaPacket
-import net.mamoe.mirai.network.protocol.timpc.packet.matchPacketId
+import net.mamoe.mirai.timpc.network.packet.PacketId
+import net.mamoe.mirai.timpc.network.packet.matchPacketId
 import kotlin.contracts.ExperimentalContracts
 import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract

+ 3 - 0
mirai-core-timpc/src/main/AndroidManifest.xml

@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest package="net.mamoe.mirai.timpc">
+</manifest>

+ 3 - 2
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/network/protocol/timpc/packet/event/MessagePacket.kt → mirai-core/src/androidMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt

@@ -1,5 +1,6 @@
-package net.mamoe.mirai.network.protocol.timpc.packet.event
+package net.mamoe.mirai.message
 
+import net.mamoe.mirai.Bot
 import net.mamoe.mirai.contact.Contact
 import net.mamoe.mirai.contact.QQ
 import net.mamoe.mirai.utils.MiraiInternalAPI
@@ -8,7 +9,7 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
  * 平台相关扩展
  */
 @UseExperimental(MiraiInternalAPI::class)
-actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> : MessagePacketBase<TSender, TSubject>() {
+actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual constructor(bot: Bot) : MessagePacketBase<TSender, TSubject>(bot) {
     //   suspend inline fun uploadImage(image: Bitmap): Image = subject.uploadImage(image)
     //suspend inline fun uploadImage(image: URL): Image = subject.uploadImage(image)
     //suspend inline fun uploadImage(image: Input): Image = subject.uploadImage(image)

+ 0 - 30
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/network/BotSession.kt

@@ -1,30 +0,0 @@
-package net.mamoe.mirai.network
-
-import android.graphics.Bitmap
-import android.graphics.BitmapFactory
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-import kotlinx.io.core.use
-import kotlinx.io.streams.inputStream
-import net.mamoe.mirai.Bot
-import net.mamoe.mirai.message.Image
-import net.mamoe.mirai.network.protocol.timpc.packet.SessionKey
-import net.mamoe.mirai.utils.MiraiInternalAPI
-import java.io.InputStream
-
-/**
- * Android 平台相关扩展. 详情查看 [BotSessionBase]
- *
- * @author Him188moe
- */
-@UseExperimental(MiraiInternalAPI::class)
-actual class BotSession internal actual constructor(
-    bot: Bot,
-    sessionKey: SessionKey
-) : BotSessionBase(bot, sessionKey) {
-
-    suspend inline fun Image.downloadAsStream(): InputStream = download().inputStream()
-    suspend inline fun Image.downloadAsBitmap(): Bitmap = withContext(Dispatchers.IO) { downloadAsStream().use { BitmapFactory.decodeStream(it) } }
-    //suspend inline fun Image.downloadAsExternalImage(): ExternalImage = download().use { it.toExternalImage() }
-
-}

+ 0 - 2
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/network/PlatformBotSession.kt

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

+ 1 - 3
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsAndroid.kt

@@ -3,8 +3,6 @@ package net.mamoe.mirai.utils
 import io.ktor.client.HttpClient
 import io.ktor.client.engine.cio.CIO
 import io.ktor.util.KtorExperimentalAPI
-import kotlinx.io.core.IoBuffer
-import kotlinx.io.core.readBytes
 import java.io.ByteArrayOutputStream
 import java.io.DataInput
 import java.io.EOFException
@@ -24,7 +22,7 @@ actual val deviceName: String get() = InetAddress.getLocalHost().hostName
  * Ktor HttpClient. 不同平台使用不同引擎.
  */
 @KtorExperimentalAPI
-internal actual val Http: HttpClient
+actual val Http: HttpClient
     get() = HttpClient(CIO)
 
 /**

+ 0 - 0
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/WeakRef.kt → mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/WeakRefAndroid.kt


+ 8 - 0
mirai-core/src/androidTest/java/ECDHTest.kt

@@ -0,0 +1,8 @@
+import net.mamoe.mirai.timpc.network.TIMProtocol
+import net.mamoe.mirai.utils.io.toUHexString
+
+
+fun main() {
+
+    println(EcdhCrypt().calShareKeyMd5ByPeerPublicKey(TIMProtocol.publicKey).toUHexString())
+}

+ 224 - 0
mirai-core/src/androidTest/java/EcdhCrypt.java

@@ -0,0 +1,224 @@
+import net.mamoe.mirai.utils.PlatformUtilsAndroidKt;
+
+import javax.crypto.KeyAgreement;
+import java.security.*;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+
+public class EcdhCrypt {
+    public static final String DEFAULT_PUB_KEY = "020b03cf3d99541f29ffec281bebbd4ea211292ac1f53d7128";
+    public static final String DEFAULT_SHARE_KEY = "4da0f614fc9f29c2054c77048a6566d7";
+    public static final String S_PUB_KEY = "04928D8850673088B343264E0C6BACB8496D697799F37211DEB25BB73906CB089FEA9639B4E0260498B51A992D50813DA8";
+    public static final String X509_S_PUB_KEY = "3046301006072A8648CE3D020106052B8104001F03320004928D8850673088B343264E0C6BACB8496D697799F37211DEB25BB73906CB089FEA9639B4E0260498B51A992D50813DA8";
+    public static byte[] _c_pri_key;
+    public static byte[] _c_pub_key;
+    private static byte[] _g_share_key;
+    private static boolean initFlg;
+    public static PrivateKey pkcs8PrivateKey;
+    private static boolean userOpenSSLLib;
+    public static PublicKey x509PublicKey;
+
+    static {
+        EcdhCrypt.initFlg = false;
+        EcdhCrypt.userOpenSSLLib = true;
+        EcdhCrypt._c_pub_key = new byte[0];
+        EcdhCrypt._c_pri_key = new byte[0];
+        EcdhCrypt._g_share_key = new byte[0];
+    }
+
+    public EcdhCrypt() {
+        /// util.loadLibrary("wtecdh", context);
+    }
+
+    public static String buf_to_string(final byte[] array) {
+        String s;
+        if (array == null) {
+            s = "";
+        } else {
+            String string = "";
+            int n = 0;
+            while (true) {
+                s = string;
+                if (n >= array.length) {
+                    break;
+                }
+                string = string + Integer.toHexString(array[n] >> 4 & 0xF) + Integer.toHexString(array[n] & 0xF);
+                ++n;
+            }
+        }
+        return s;
+    }
+
+    private byte[] calShareKeyByBouncycastle(final byte[] array) {
+        String str = "3046301006072A8648CE3D020106052B8104001F03320004";
+        try {
+            if (array.length < 30) {
+                str = "302E301006072A8648CE3D020106052B8104001F031A00";
+            }
+            final PublicKey constructX509PublicKey = this.constructX509PublicKey(str + buf_to_string(array));
+            final KeyAgreement instance = KeyAgreement.getInstance("ECDH", "BC");
+            instance.init(EcdhCrypt.pkcs8PrivateKey);
+            instance.doPhase(constructX509PublicKey, true);
+            final byte[] generateSecret = instance.generateSecret();
+            return PlatformUtilsAndroidKt.md5(generateSecret);
+        } catch (ExceptionInInitializerError | Exception exceptionInInitializerError) {
+            exceptionInInitializerError.printStackTrace();
+            return null;
+        }
+    }
+
+    private byte[] calShareKeyByOpenSSL(final String s, final String str, final String s2) {
+        //if (this.GenECDHKeyEx(s2, str, s) == 0) {
+        return EcdhCrypt._g_share_key;
+        //}
+        //   return null;
+    }
+
+    private PublicKey constructX509PublicKey(final String str) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException {
+        return KeyFactory.getInstance("EC", "BC").generatePublic(new X509EncodedKeySpec(string_to_buf(str)));
+    }
+
+    public static byte[] string_to_buf(final String s) {
+        int i = 0;
+        if (s == null) {
+            return new byte[0];
+        }
+        final byte[] array = new byte[s.length() / 2];
+        while (i < s.length() / 2) {
+            array[i] = (byte) ((get_char((byte) s.charAt(i * 2)) << 4) + get_char((byte) s.charAt(i * 2 + 1)));
+            ++i;
+        }
+        return array;
+    }
+
+    public static byte get_char(final byte b) {
+        if (b >= 48 && b <= 57) {
+            return (byte) (b - 48);
+        }
+        if (b >= 97 && b <= 102) {
+            return (byte) (b - 97 + 10);
+        }
+        if (b >= 65 && b <= 70) {
+            return (byte) (b - 65 + 10);
+        }
+        return 0;
+    }
+
+    private int initShareKeyByBouncycastle() {
+        try {
+            final KeyPairGenerator instance = KeyPairGenerator.getInstance("EC", "BC");
+            instance.initialize(new ECGenParameterSpec("secp192k1"));
+            final KeyPair genKeyPair = instance.genKeyPair();
+            final PublicKey public1 = genKeyPair.getPublic();
+            final byte[] encoded = public1.getEncoded();
+            final PrivateKey private1 = genKeyPair.getPrivate();
+            private1.getEncoded();
+            final PublicKey constructX509PublicKey = this.constructX509PublicKey("3046301006072A8648CE3D020106052B8104001F03320004928D8850673088B343264E0C6BACB8496D697799F37211DEB25BB73906CB089FEA9639B4E0260498B51A992D50813DA8");
+            final KeyAgreement instance2 = KeyAgreement.getInstance("ECDH", "BC");
+            instance2.init(private1);
+            instance2.doPhase(constructX509PublicKey, true);
+            EcdhCrypt._g_share_key = PlatformUtilsAndroidKt.md5(instance2.generateSecret());
+            System.arraycopy(encoded, 23, EcdhCrypt._c_pub_key = new byte[49], 0, 49);
+            EcdhCrypt.x509PublicKey = public1;
+            EcdhCrypt.pkcs8PrivateKey = private1;
+            return 0;
+        } catch (ExceptionInInitializerError exceptionInInitializerError) {
+            exceptionInInitializerError.printStackTrace();
+            return -1;
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            return -2;
+        }
+    }
+
+    private int initShareKeyByOpenSSL() {
+        // if (Build$VERSION.SDK_INT >= 23 || this.GenereateKey() != 0) {
+        //     return -1;
+        // }
+        if (EcdhCrypt._c_pub_key == null || EcdhCrypt._c_pub_key.length == 0 || EcdhCrypt._c_pri_key == null || EcdhCrypt._c_pri_key.length == 0 || EcdhCrypt._g_share_key == null || EcdhCrypt._g_share_key.length == 0) {
+            return -2;
+        }
+        return 0;
+    }
+
+    public native int GenECDHKeyEx(final String p0, final String p1, final String p2);
+
+    public int GenereateKey() {
+        try {
+            synchronized (EcdhCrypt.class) {
+                return this.GenECDHKeyEx("04928D8850673088B343264E0C6BACB8496D697799F37211DEB25BB73906CB089FEA9639B4E0260498B51A992D50813DA8", "", "");
+            }
+        } catch (UnsatisfiedLinkError unsatisfiedLinkError) {
+            unsatisfiedLinkError.printStackTrace();
+            return -1;
+        } catch (RuntimeException ex) {
+            return -2;
+        } catch (Exception ex2) {
+            return -3;
+        } catch (Error error) {
+            return -4;
+        }
+    }
+
+    public byte[] calShareKeyMd5ByPeerPublicKey(final byte[] array) {
+        if (EcdhCrypt.userOpenSSLLib) {
+            return this.calShareKeyByOpenSSL(buf_to_string(EcdhCrypt._c_pri_key), buf_to_string(EcdhCrypt._c_pub_key), buf_to_string(array));
+        }
+        return this.calShareKeyByBouncycastle(array);
+    }
+
+    public byte[] get_c_pub_key() {
+        return EcdhCrypt._c_pub_key.clone();
+    }
+
+    public byte[] get_g_share_key() {
+        return EcdhCrypt._g_share_key.clone();
+    }
+
+    public int initShareKey() {
+        if (EcdhCrypt.initFlg) {
+            return 0;
+        }
+        EcdhCrypt.initFlg = true;
+        if (this.initShareKeyByOpenSSL() == 0) {
+            EcdhCrypt.userOpenSSLLib = true;
+            return 0;
+        }
+        if (this.initShareKeyByBouncycastle() == 0) {
+            EcdhCrypt.userOpenSSLLib = false;
+            return 0;
+        }
+        return this.initShareKeyByDefault();
+    }
+
+    public int initShareKeyByDefault() {
+        // EcdhCrypt._c_pub_key = util.string_to_buf("020b03cf3d99541f29ffec281bebbd4ea211292ac1f53d7128");
+        // EcdhCrypt._g_share_key = util.string_to_buf("4da0f614fc9f29c2054c77048a6566d7");
+        return 0;
+    }
+
+    public void set_c_pri_key(final byte[] array) {
+        if (array != null) {
+            EcdhCrypt._c_pri_key = array.clone();
+            return;
+        }
+        EcdhCrypt._c_pri_key = new byte[0];
+    }
+
+    public void set_c_pub_key(final byte[] array) {
+        if (array != null) {
+            EcdhCrypt._c_pub_key = array.clone();
+            return;
+        }
+        EcdhCrypt._c_pub_key = new byte[0];
+    }
+
+    public void set_g_share_key(final byte[] array) {
+        if (array != null) {
+            EcdhCrypt._g_share_key = array.clone();
+            return;
+        }
+        EcdhCrypt._g_share_key = new byte[0];
+    }
+}

+ 70 - 56
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt

@@ -1,20 +1,23 @@
-@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "FunctionName", "NOTHING_TO_INLINE")
+@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "FunctionName")
 
 package net.mamoe.mirai
 
+import io.ktor.util.KtorExperimentalAPI
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Deferred
-import kotlinx.coroutines.Job
+import kotlinx.io.OutputStream
+import kotlinx.io.core.ByteReadPacket
+import kotlinx.io.core.use
 import net.mamoe.mirai.contact.*
+import net.mamoe.mirai.message.data.Image
 import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult
+import net.mamoe.mirai.network.data.AddFriendResult
+import net.mamoe.mirai.network.data.ImageLink
 import net.mamoe.mirai.utils.BotConfiguration
 import net.mamoe.mirai.utils.GroupNotFoundException
+import net.mamoe.mirai.utils.MiraiInternalAPI
 import net.mamoe.mirai.utils.MiraiLogger
+import net.mamoe.mirai.utils.io.transferTo
 import kotlin.coroutines.CoroutineContext
-import kotlin.coroutines.coroutineContext
-import kotlin.jvm.JvmSynthetic
-
 
 /**
  * Mirai 的机器人. 一个机器人实例登录一个 QQ 账号.
@@ -22,77 +25,60 @@ import kotlin.jvm.JvmSynthetic
  *
  * @see Contact
  */
-interface Bot : CoroutineScope {
+abstract class Bot : CoroutineScope {
+    @UseExperimental(MiraiInternalAPI::class)
     companion object {
-        suspend inline operator fun invoke(account: BotAccount, logger: MiraiLogger): Bot = BotImpl(account, logger, coroutineContext)
-        suspend inline operator fun invoke(account: BotAccount): Bot = BotImpl(account, context = coroutineContext)
-        @JvmSynthetic
-        suspend inline operator fun invoke(qq: UInt, password: String): Bot = BotImpl(BotAccount(qq, password), context = coroutineContext)
-
-        suspend inline operator fun invoke(qq: Long, password: String): Bot = BotImpl(BotAccount(qq.toUInt(), password), context = coroutineContext)
-        operator fun invoke(qq: Long, password: String, context: CoroutineContext): Bot = BotImpl(BotAccount(qq, password), context = context)
-        @JvmSynthetic
-        operator fun invoke(qq: UInt, password: String, context: CoroutineContext): Bot = BotImpl(BotAccount(qq, password), context = context)
-
-        operator fun invoke(account: BotAccount, context: CoroutineContext): Bot = BotImpl(account, context = context)
-
-
         inline fun forEachInstance(block: (Bot) -> Unit) = BotImpl.forEachInstance(block)
 
-        fun instanceWhose(qq: UInt): Bot = BotImpl.instanceWhose(qq = qq)
+        fun instanceWhose(qq: Long): Bot = BotImpl.instanceWhose(qq = qq)
     }
 
     /**
      * 账号信息
      */
-    val account: BotAccount
+    abstract val account: BotAccount
 
     /**
      * 日志记录器
      */
-    val logger: MiraiLogger
+    abstract val logger: MiraiLogger
 
-    override val coroutineContext: CoroutineContext
+    abstract override val coroutineContext: CoroutineContext
 
     // region contacts
 
     /**
      * 与这个机器人相关的 QQ 列表. 机器人与 QQ 不一定是好友
      */
-    val qqs: ContactList<QQ>
-
-    /**
-     * 获取缓存的 QQ 对象. 若没有对应的缓存, 则会线程安全地创建一个.
-     */
-    fun getQQ(id: UInt): QQ
+    abstract val qqs: ContactList<QQ>
 
     /**
      * 获取缓存的 QQ 对象. 若没有对应的缓存, 则会线程安全地创建一个.
      */
-    fun getQQ(id: Long): QQ
+    abstract fun getQQ(id: Long): QQ
 
     /**
      * 与这个机器人相关的群列表. 机器人不一定是群成员.
      */
-    val groups: ContactList<Group>
+    abstract val groups: ContactList<Group>
 
     /**
      * 获取缓存的群对象. 若没有对应的缓存, 则会线程安全地创建一个.
      * 若 [id] 无效, 将会抛出 [GroupNotFoundException]
      */
-    suspend fun getGroup(id: GroupId): Group
+    abstract suspend fun getGroup(id: GroupId): Group
 
     /**
      * 获取缓存的群对象. 若没有对应的缓存, 则会线程安全地创建一个.
      * 若 [internalId] 无效, 将会抛出 [GroupNotFoundException]
      */
-    suspend fun getGroup(internalId: GroupInternalId): Group
+    abstract suspend fun getGroup(internalId: GroupInternalId): Group
 
     /**
      * 获取缓存的群对象. 若没有对应的缓存, 则会线程安全地创建一个.
      * 若 [id] 无效, 将会抛出 [GroupNotFoundException]
      */
-    suspend fun getGroup(id: Long): Group
+    abstract suspend fun getGroup(id: Long): Group
 
     // endregion
 
@@ -101,36 +87,64 @@ interface Bot : CoroutineScope {
     /**
      * 网络模块
      */
-    val network: BotNetworkHandler<*>
+    abstract val network: BotNetworkHandler
 
     /**
-     * [关闭][BotNetworkHandler.close]网络处理器, 取消所有运行在 [BotNetworkHandler] 下的协程.
-     * 然后重新启动并尝试登录
+     * 使用在默认配置基础上修改的配置进行登录
      */
-    fun tryReinitializeNetworkHandler(
-        configuration: BotConfiguration,
-        cause: Throwable? = null
-    ): Job
+    suspend inline fun login(configuration: BotConfiguration.() -> Unit) {
+        return this.login(BotConfiguration().apply(configuration))
+    }
+
+    /**
+     * 使用特定配置进行登录
+     */
+    abstract suspend fun login(configuration: BotConfiguration = BotConfiguration.Default)
+    // endregion
+
+
+    // region actions
+
+    abstract suspend fun Image.getLink(): ImageLink
+
+    suspend fun Image.downloadAsByteArray(): ByteArray = getLink().downloadAsByteArray()
+
+    suspend fun Image.download(): ByteReadPacket = getLink().download()
 
     /**
-     * [关闭][BotNetworkHandler.close]网络处理器, 取消所有运行在 [BotNetworkHandler] 下的协程.
-     * 然后重新启动并尝试登录
+     * 添加一个好友
+     *
+     * @param message 若需要验证请求时的验证消息.
+     * @param remark 好友备注
      */
-    suspend fun reinitializeNetworkHandler(
-        configuration: BotConfiguration,
-        cause: Throwable? = null
-    ): LoginResult
+    abstract suspend fun addFriend(id: Long, message: String? = null, remark: String? = null): AddFriendResult
 
     /**
-     * [关闭][BotNetworkHandler.close]网络处理器, 取消所有运行在 [BotNetworkHandler] 下的协程.
-     * 然后重新启动并尝试登录
+     * 同意来自陌生人的加好友请求
      */
-    fun reinitializeNetworkHandlerAsync(
-        configuration: BotConfiguration,
-        cause: Throwable? = null
-    ): Deferred<LoginResult>
+    abstract suspend fun approveFriendAddRequest(id: Long, remark: String?)
 
     // endregion
 
-    fun close()
+    abstract fun close(throwable: Throwable?)
+
+    // region extensions
+
+    fun Int.qq(): QQ = getQQ(this.toLong())
+    fun Long.qq(): QQ = getQQ(this)
+
+    suspend inline fun Int.group(): Group = getGroup(this.toLong())
+    suspend inline fun Long.group(): Group = getGroup(this)
+    suspend inline fun GroupInternalId.group(): Group = getGroup(this)
+    suspend inline fun GroupId.group(): Group = getGroup(this)
+
+
+    /**
+     * 需要调用者自行 close [output]
+     */
+    @UseExperimental(KtorExperimentalAPI::class)
+    suspend inline fun Image.downloadTo(output: OutputStream) =
+        download().use { input -> input.transferTo(output) }
+
+    // endregion
 }

+ 2 - 4
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAccount.kt

@@ -3,8 +3,6 @@
 package net.mamoe.mirai
 
 data class BotAccount(
-    val id: UInt,
+    val id: Long,
     val password: String
-) {
-    constructor(id: Long, password: String) : this(id.toUInt(), password)
-}
+)

ファイルの差分が大きいため隠しています
+ 0 - 122
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAddFriend.kt


+ 70 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotFactory.kt

@@ -0,0 +1,70 @@
+package net.mamoe.mirai
+
+import kotlinx.coroutines.CoroutineScope
+import net.mamoe.mirai.utils.MiraiLogger
+
+/**
+ * 构造 [Bot] 的工厂.
+ *
+ * 在协议模块中有各自的实现.
+ * - `mirai-core-timpc`: `TIMPC`
+ * - `mirai-core-qqandroid`: `QQAndroid`
+ */
+@Suppress("FunctionName")
+interface BotFactory {
+    /**
+     * 在当前 CoroutineScope 下构造 Bot 实例
+     * 该 Bot 实例的生命周期将会跟随这个 CoroutineScope.
+     * 这个 CoroutineScope 也会等待 Bot 的结束才会结束.
+     *
+     * ```kotlin
+     * suspend fun myProcess(){
+     *   TIMPC.Bot(account, logger)
+     * }
+     * ```
+     */
+    suspend fun Bot(account: BotAccount, logger: MiraiLogger? = null): Bot
+
+    /**
+     * 在当前 CoroutineScope 下构造 Bot 实例
+     * 该 Bot 实例的生命周期将会跟随这个 CoroutineScope.
+     * 这个 CoroutineScope 也会等待 Bot 的结束才会结束.
+     *
+     * ```kotlin
+     * suspend fun myProcess(){
+     *   TIMPC.Bot(account, logger)
+     * }
+     * ```
+     */
+    suspend fun Bot(qq: Long, password: String, logger: MiraiLogger? = null): Bot
+
+    /**
+     * 在特定的 CoroutineScope 下构造 Bot 实例
+     * 该 Bot 实例的生命周期将会跟随这个 CoroutineScope.
+     * 这个 CoroutineScope 也会等待 Bot 的结束才会结束.
+     *
+     * ```kotlin
+     * fun myProcess(){
+     *   TIMPC.run {
+     *     GlobalScope.Bot(account, logger)
+     *   }
+     * }
+     * ```
+     */
+    fun CoroutineScope.Bot(qq: Long, password: String, logger: MiraiLogger? = null): Bot
+
+    /**
+     * 在特定的 CoroutineScope 下构造 Bot 实例
+     * 该 Bot 实例的生命周期将会跟随这个 CoroutineScope.
+     * 这个 CoroutineScope 也会等待 Bot 的结束才会结束.
+     *
+     * ```kotlin
+     * fun myProcess(){
+     *   TIMPC.run {
+     *     GlobalScope.Bot(account, logger)
+     *   }
+     * }
+     * ```
+     */
+    fun CoroutineScope.Bot(account: BotAccount, logger: MiraiLogger? = null): Bot
+}

+ 5 - 61
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotHelper.kt

@@ -1,73 +1,26 @@
 @file:JvmMultifileClass
 @file:JvmName("BotHelperKt")
-@file:Suppress("unused", "EXPERIMENTAL_API_USAGE", "NOTHING_TO_INLINE")
+@file:Suppress("unused", "EXPERIMENTAL_API_USAGE")
 
 package net.mamoe.mirai
 
-import net.mamoe.mirai.contact.*
-import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.BotSession
-import net.mamoe.mirai.network.protocol.timpc.TIMBotNetworkHandler
-import net.mamoe.mirai.network.protocol.timpc.packet.OutgoingPacket
-import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult
-import net.mamoe.mirai.network.protocol.timpc.packet.login.requireSuccess
 import net.mamoe.mirai.utils.BotConfiguration
-import net.mamoe.mirai.utils.internal.PositiveNumbers
-import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
 import kotlin.contracts.ExperimentalContracts
 import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmMultifileClass
 import kotlin.jvm.JvmName
-import kotlin.jvm.JvmOverloads
 
 /*
  * 在 [Bot] 中的方法的捷径
  */
 
 //Contacts
-suspend inline fun Bot.getGroup(id: UInt): Group = this.getGroup(GroupId(id))
-
-
-/**
- * 以 [BotSession] 作为接收器 (receiver) 并调用 [block], 返回 [block] 的返回值.
- * 这个方法将能帮助使用在 [BotSession] 中定义的一些扩展方法, 如 [BotSession.sendAndExpectAsync]
- */
-@UseExperimental(ExperimentalContracts::class)
-inline fun <R> Bot.withSession(block: BotSession.() -> R): R {
-    contract {
-        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
-    }
-    return with(this.network.session) { block() }
-}
-
-/**
- * 发送数据包
- * @throws IllegalStateException 当 [BotNetworkHandler.socket] 未开启时
- */
-internal suspend inline fun Bot.sendPacket(packet: OutgoingPacket) =
-    (this.network as TIMBotNetworkHandler).socket.sendPacket(packet)
-
-/**
- * 使用在默认配置基础上修改的配置进行登录
- */
-@UseExperimental(ExperimentalContracts::class)
-suspend inline fun Bot.login(configuration: BotConfiguration.() -> Unit): LoginResult {
-    contract {
-        callsInPlace(configuration, InvocationKind.EXACTLY_ONCE)
-    }
-    return this.reinitializeNetworkHandler(BotConfiguration().apply(configuration))
-}
-
-/**
- * 使用默认的配置 ([BotConfiguration.Default]) 登录, 返回登录结果
- */
-suspend inline fun Bot.login(): LoginResult = this.reinitializeNetworkHandler(BotConfiguration.Default)
-
 /**
  * 使用默认的配置 ([BotConfiguration.Default]) 登录, 返回 [this]
  */
-suspend inline fun Bot.alsoLogin(): Bot = apply { login().requireSuccess() }
+suspend inline fun Bot.alsoLogin(configuration: BotConfiguration = BotConfiguration.Default): Bot =
+    apply { login(configuration) }
 
 /**
  * 使用在默认配置基础上修改的配置进行登录, 返回 [this]
@@ -77,20 +30,11 @@ suspend inline fun Bot.alsoLogin(configuration: BotConfiguration.() -> Unit): Bo
     contract {
         callsInPlace(configuration, InvocationKind.EXACTLY_ONCE)
     }
-    this.reinitializeNetworkHandler(BotConfiguration().apply(configuration)).requireSuccess()
+    this.login(configuration)
     return this
 }
 
-/**
- * 使用默认的配置 ([BotConfiguration.Default]) 登录, 返回 [this]
- */
-suspend inline fun Bot.alsoLogin(message: String): Bot {
-    return this.apply {
-        login().requireSuccess { message } // requireSuccess is inline, so no performance waste
-    }
-}
-
 /**
  * 取得机器人的 QQ 号
  */
-inline val Bot.qqAccount: UInt get() = this.account.id
+inline val Bot.qqAccount: Long get() = this.account.id

+ 29 - 120
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt

@@ -2,54 +2,43 @@
 
 package net.mamoe.mirai
 
-import kotlinx.coroutines.*
-import net.mamoe.mirai.contact.*
-import net.mamoe.mirai.contact.internal.Group
-import net.mamoe.mirai.contact.internal.QQ
+import kotlinx.coroutines.CoroutineExceptionHandler
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
 import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.network.protocol.timpc.TIMBotNetworkHandler
-import net.mamoe.mirai.network.protocol.timpc.packet.KnownPacketId
-import net.mamoe.mirai.network.protocol.timpc.packet.action.GroupNotFound
-import net.mamoe.mirai.network.protocol.timpc.packet.action.GroupPacket
-import net.mamoe.mirai.network.protocol.timpc.packet.action.RawGroupInfo
-import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult
-import net.mamoe.mirai.network.protocol.timpc.packet.login.isSuccess
-import net.mamoe.mirai.network.qqAccount
-import net.mamoe.mirai.utils.*
-import net.mamoe.mirai.utils.internal.PositiveNumbers
-import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
-import net.mamoe.mirai.utils.io.inline
+import net.mamoe.mirai.utils.DefaultLogger
+import net.mamoe.mirai.utils.LockFreeLinkedList
+import net.mamoe.mirai.utils.MiraiInternalAPI
+import net.mamoe.mirai.utils.MiraiLogger
 import net.mamoe.mirai.utils.io.logStacktrace
 import kotlin.coroutines.CoroutineContext
-import kotlin.jvm.JvmSynthetic
 
-@PublishedApi
-internal class BotImpl @PublishedApi internal constructor(
+/*
+ * 泛型 N 不需要向外(接口)暴露.
+ */
+@MiraiInternalAPI
+abstract class BotImpl<N : BotNetworkHandler> constructor(
     override val account: BotAccount,
     override val logger: MiraiLogger = DefaultLogger("Bot(" + account.id + ")"),
     context: CoroutineContext
-) : Bot, CoroutineScope {
+) : Bot(), CoroutineScope {
     private val supervisorJob = SupervisorJob(context[Job])
     override val coroutineContext: CoroutineContext =
         context + supervisorJob + CoroutineExceptionHandler { _, e -> e.logStacktrace("An exception was thrown under a coroutine of Bot") }
 
     init {
-        launch {
-            instances.addLast(this@BotImpl)
-        }
+        @Suppress("LeakingThis")
+        instances.addLast(this)
     }
 
     companion object {
-        init {
-            KnownPacketId.values() /* load id classes */
-        }
-
         @PublishedApi
         internal val instances: LockFreeLinkedList<Bot> = LockFreeLinkedList()
 
         inline fun forEachInstance(block: (Bot) -> Unit) = instances.forEach(block)
 
-        fun instanceWhose(qq: UInt): Bot {
+        fun instanceWhose(qq: Long): Bot {
             instances.forEach {
                 if (it.qqAccount == qq) {
                     return it
@@ -63,101 +52,21 @@ internal class BotImpl @PublishedApi internal constructor(
 
     // region network
 
-    override val network: BotNetworkHandler<*> get() = _network
-    private lateinit var _network: BotNetworkHandler<*>
-
-    override fun tryReinitializeNetworkHandler(// shouldn't be suspend!! This function MUST NOT inherit the context from the caller because the caller(NetworkHandler) is going to close
-        configuration: BotConfiguration,
-        cause: Throwable?
-    ): Job = launch {
-        repeat(configuration.reconnectionRetryTimes) {
-            if (reinitializeNetworkHandlerAsync(configuration, cause).await().isSuccess()) {
-                logger.info("Reconnected successfully")
-                return@launch
-            } else {
-                delay(configuration.reconnectPeriodMillis)
-            }
-        }
-    }
-
-    override suspend fun reinitializeNetworkHandler(
-        configuration: BotConfiguration,
-        cause: Throwable?
-    ): LoginResult {
-        logger.info("BotAccount: ${qqAccount.toLong()}")
-        logger.info("Initializing BotNetworkHandler")
-        try {
-            if (::_network.isInitialized) {
-                _network.close(cause)
-            }
-        } catch (e: Exception) {
-            logger.error("Cannot close network handler", e)
-        }
-        _network = TIMBotNetworkHandler(this.coroutineContext + configuration, this)
-
-        return _network.login()
-    }
-
-    override fun reinitializeNetworkHandlerAsync(
-        configuration: BotConfiguration,
-        cause: Throwable?
-    ): Deferred<LoginResult> = async { reinitializeNetworkHandler(configuration, cause) }
-
+    abstract override val network: N
     // endregion
 
-    // region contacts
-    override val groups: ContactList<Group> = ContactList(LockFreeLinkedList())
-
-    override val qqs: ContactList<QQ> = ContactList(LockFreeLinkedList())
-
-    /**
-     * 线程安全地获取缓存的 QQ 对象. 若没有对应的缓存, 则会创建一个.
-     */
     @UseExperimental(MiraiInternalAPI::class)
-    @JvmSynthetic
-    override fun getQQ(id: UInt): QQ = qqs.delegate.getOrAdd(id) { QQ(this, id, coroutineContext) }
-
-    @UseExperimental(MiraiInternalAPI::class)
-    override fun getQQ(@PositiveNumbers id: Long): QQ = getQQ(id.coerceAtLeastOrFail(0).toUInt())
-
-    override suspend fun getGroup(internalId: GroupInternalId): Group = getGroup(internalId.toId())
-
-    @UseExperimental(MiraiInternalAPI::class, ExperimentalUnsignedTypes::class)
-    override suspend fun getGroup(id: GroupId): Group = groups.delegate.getOrNull(id.value) ?: inline {
-        val info: RawGroupInfo = try {
-            when (val response =
-                withSession { GroupPacket.QueryGroupInfo(qqAccount, id.toInternalId(), sessionKey).sendAndExpect<GroupPacket.InfoResponse>() }) {
-                is RawGroupInfo -> response
-                is GroupNotFound -> throw GroupNotFoundException("id=${id.value.toLong()}")
-                else -> assertUnreachable()
-            }
-        } catch (e: Exception) {
-            throw IllegalStateException("Cannot obtain group info for id ${id.value.toLong()}", e)
-        }
-
-        return groups.delegate.getOrAdd(id.value) { Group(this, id, info, coroutineContext) }
-    }
-
-    @UseExperimental(MiraiInternalAPI::class)
-    override suspend fun getGroup(@PositiveNumbers id: Long): Group = id.coerceAtLeastOrFail(0).toUInt().let {
-        groups.delegate.getOrNull(it) ?: inline {
-            val info: RawGroupInfo = try {
-                withSession { GroupPacket.QueryGroupInfo(qqAccount, GroupId(it).toInternalId(), sessionKey).sendAndExpect() }
-            } catch (e: Exception) {
-                e.logStacktrace()
-                error("Cannot obtain group info for id ${it.toLong()}")
-            }
-
-            return groups.delegate.getOrAdd(it) { Group(this, GroupId(it), info, coroutineContext) }
+    override fun close(throwable: Throwable?) {
+        if (throwable == null) {
+            network.close()
+            this.supervisorJob.complete()
+            groups.delegate.clear()
+            qqs.delegate.clear()
+        } else {
+            network.close(throwable)
+            this.supervisorJob.completeExceptionally(throwable)
+            groups.delegate.clear()
+            qqs.delegate.clear()
         }
     }
-    // endregion
-
-    @UseExperimental(MiraiInternalAPI::class)
-    override fun close() {
-        _network.close()
-        this.supervisorJob.complete()
-        groups.delegate.clear()
-        qqs.delegate.clear()
-    }
 }

+ 12 - 15
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt

@@ -3,15 +3,9 @@
 package net.mamoe.mirai.contact
 
 import net.mamoe.mirai.Bot
-import net.mamoe.mirai.message.Message
-import net.mamoe.mirai.message.MessageChain
-import net.mamoe.mirai.message.chain
-import net.mamoe.mirai.message.singleChain
-import net.mamoe.mirai.network.BotSession
-import net.mamoe.mirai.utils.LockFreeLinkedList
-import net.mamoe.mirai.utils.MiraiInternalAPI
-import net.mamoe.mirai.utils.joinToString
-import net.mamoe.mirai.withSession
+import net.mamoe.mirai.message.data.*
+import net.mamoe.mirai.utils.ExternalImage
+import net.mamoe.mirai.utils.WeakRefProperty
 import kotlin.contracts.ExperimentalContracts
 import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
@@ -26,12 +20,13 @@ interface Contact {
     /**
      * 这个联系人所属 [Bot]
      */
-    val bot: Bot
+    @WeakRefProperty
+    val bot: Bot // weak ref
 
     /**
      * 可以是 QQ 号码或者群号码 [GroupId].
      */
-    val id: UInt
+    val id: Long
 
     /**
      * 向这个对象发送消息.
@@ -39,6 +34,8 @@ interface Contact {
      * 速度太快会被服务器屏蔽(无响应). 在测试中不延迟地发送 6 条消息就会被屏蔽之后的数据包 1 秒左右.
      */
     suspend fun sendMessage(message: MessageChain)
+
+    suspend fun uploadImage(image: ExternalImage): ImageId
 }
 
 suspend inline fun Contact.sendMessage(message: Message) = sendMessage(message.chain())
@@ -46,13 +43,13 @@ suspend inline fun Contact.sendMessage(message: Message) = sendMessage(message.c
 suspend inline fun Contact.sendMessage(plain: String) = sendMessage(plain.singleChain())
 
 /**
- * 以 [BotSession] 作为接收器 (receiver) 并调用 [block], 返回 [block] 的返回值.
- * 这个方法将能帮助使用在 [BotSession] 中定义的一些扩展方法, 如 [BotSession.sendAndExpectAsync]
+ * 以 [Bot] 作为接收器 (receiver) 并调用 [block], 返回 [block] 的返回值.
+ * 这个方法将能帮助使用在 [Bot] 中定义的一些扩展方法
  */
 @UseExperimental(ExperimentalContracts::class)
-inline fun <R> Contact.withSession(block: BotSession.() -> R): R {
+inline fun <R> Contact.withBot(block: Bot.() -> R): R {
     contract {
         callsInPlace(block, InvocationKind.EXACTLY_ONCE)
     }
-    return bot.withSession(block)
+    return bot.run(block)
 }

+ 7 - 7
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt

@@ -12,7 +12,7 @@ import net.mamoe.mirai.utils.joinToString
  */
 @UseExperimental(MiraiInternalAPI::class)
 @Suppress("unused")
-class ContactList<C : Contact>(@PublishedApi internal val delegate: LockFreeLinkedList<C>) {
+class ContactList<C : Contact>(@MiraiInternalAPI val delegate: LockFreeLinkedList<C>) {
     /**
      * ID 列表的字符串表示.
      * 如:
@@ -22,9 +22,9 @@ class ContactList<C : Contact>(@PublishedApi internal val delegate: LockFreeLink
      */
     val idContentString: String get() = "[" + buildString { delegate.forEach { append(it.id).append(", ") } }.dropLast(2) + "]"
 
-    operator fun get(id: UInt): C = delegate[id]
-    fun getOrNull(id: UInt): C? = delegate.getOrNull(id)
-    fun containsId(id: UInt): Boolean = delegate.getOrNull(id) != null
+    operator fun get(id: Long): C = delegate[id]
+    fun getOrNull(id: Long): C? = delegate.getOrNull(id)
+    fun containsId(id: Long): Boolean = delegate.getOrNull(id) != null
 
     val size: Int get() = delegate.size
     operator fun contains(element: C): Boolean = delegate.contains(element)
@@ -35,14 +35,14 @@ class ContactList<C : Contact>(@PublishedApi internal val delegate: LockFreeLink
     override fun toString(): String = delegate.joinToString(separator = ", ", prefix = "ContactList(", postfix = ")")
 }
 
-operator fun <C : Contact> LockFreeLinkedList<C>.get(id: UInt): C {
+operator fun <C : Contact> LockFreeLinkedList<C>.get(id: Long): C {
     forEach { if (it.id == id) return it }
     throw NoSuchElementException()
 }
 
-fun <C : Contact> LockFreeLinkedList<C>.getOrNull(id: UInt): C? {
+fun <C : Contact> LockFreeLinkedList<C>.getOrNull(id: Long): C? {
     forEach { if (it.id == id) return it }
     return null
 }
 
-fun <C : Contact> LockFreeLinkedList<C>.getOrAdd(id: UInt, supplier: () -> C): C = filteringGetOrAdd({ it.id == id }, supplier)
+fun <C : Contact> LockFreeLinkedList<C>.getOrAdd(id: Long, supplier: () -> C): C = filteringGetOrAdd({ it.id == id }, supplier)

+ 7 - 18
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt

@@ -3,10 +3,7 @@
 package net.mamoe.mirai.contact
 
 import kotlinx.coroutines.CoroutineScope
-import net.mamoe.mirai.network.protocol.timpc.packet.action.GroupInfo
-import net.mamoe.mirai.network.protocol.timpc.packet.action.QuitGroupResponse
-import net.mamoe.mirai.utils.MiraiExperimentalAPI
-import net.mamoe.mirai.utils.internal.PositiveNumbers
+import net.mamoe.mirai.network.data.GroupInfo
 import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
 
 
@@ -53,7 +50,7 @@ interface Group : Contact, CoroutineScope/*, Map<UInt, Member>*/ { // TODO: 2019
     /**
      * 获取群成员. 若此 ID 的成员不存在, 则会抛出 [kotlin.NoSuchElementException]
      */
-    fun getMember(id: UInt): Member
+    fun getMember(id: Long): Member
 
     /**
      * 更新群资料. 群资料会与服务器事件同步事件更新, 一般情况下不需要手动更新.
@@ -67,7 +64,7 @@ interface Group : Contact, CoroutineScope/*, Map<UInt, Member>*/ { // TODO: 2019
      *
      * @see QuitGroupResponse.isSuccess 判断是否成功
      */
-    suspend fun quit(): QuitGroupResponse
+    suspend fun quit(): Boolean
 
     fun toFullString(): String = "Group(id=${this.id}, name=$name, owner=${owner.id}, members=${members.idContentString})"
 }
@@ -81,27 +78,19 @@ interface Group : Contact, CoroutineScope/*, Map<UInt, Member>*/ { // TODO: 2019
  * @see GroupInternalId.toId 由 [GroupInternalId] 转换为 [GroupId]
  * @see GroupId.toInternalId 由 [GroupId] 转换为 [GroupInternalId]
  */
-inline class GroupId(inline val value: UInt)
-
-/**
- * 将 [this] 转为 [GroupId].
- */
-@Suppress("NOTHING_TO_INLINE")
-inline fun UInt.groupId(): GroupId = GroupId(this)
+inline class GroupId(inline val value: Long)
 
 /**
  * 将 [this] 转为 [GroupInternalId].
  */
-@Suppress("NOTHING_TO_INLINE")
-inline fun UInt.groupInternalId(): GroupInternalId = GroupInternalId(this)
+fun Long.groupInternalId(): GroupInternalId = GroupInternalId(this)
 
 /**
  * 将无符号整数格式的 [Long] 转为 [GroupId].
  *
  * 注: 在 Java 中常用 [Long] 来表示 [UInt]
  */
-fun @receiver:PositiveNumbers Long.groupId(): GroupId =
-    GroupId(this.coerceAtLeastOrFail(0).toUInt())
+fun Long.groupId(): GroupId = GroupId(this.coerceAtLeastOrFail(0))
 
 /**
  * 一些群 API 使用的 ID. 在使用时会特别注明
@@ -111,4 +100,4 @@ fun @receiver:PositiveNumbers Long.groupId(): GroupId =
  * @see GroupInternalId.toId 由 [GroupInternalId] 转换为 [GroupId]
  * @see GroupId.toInternalId 由 [GroupId] 转换为 [GroupInternalId]
  */
-inline class GroupInternalId(inline val value: UInt)
+inline class GroupInternalId(inline val value: Long)

+ 11 - 11
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/GroupIdConvertions.kt

@@ -6,7 +6,7 @@ import kotlin.math.pow
 
 
 @Suppress("ObjectPropertyName")
-private val `10EXP6` = 10.0.pow(6).toUInt()
+private val `10EXP6` = 10.0.pow(6)
 
 
 fun GroupId.toInternalId(): GroupInternalId {
@@ -23,12 +23,12 @@ fun GroupId.toInternalId(): GroupInternalId {
             in 1..10 -> plusLeft(202, 6)
             in 11..19 -> plusLeft(469, 6)
             in 20..66 -> plusLeft(208, 7)
-            in 67..156 ->  plusLeft(1943, 6)
+            in 67..156 -> plusLeft(1943, 6)
             in 157..209 -> plusLeft(199, 7)
             in 210..309 -> plusLeft(389, 7)
             in 310..499 -> plusLeft(349, 7)
             else -> null
-        }?.toUInt() ?: this.value
+        }?.toLong() ?: this.value
     )
 }
 
@@ -36,17 +36,17 @@ fun GroupInternalId.toId(): GroupId = with(value.toString()) {
     if (value < `10EXP6`) {
         return GroupId(value)
     }
-    val left: UInt = this.dropLast(6).toUInt()
+    val left = this.dropLast(6).toLong()
 
     return GroupId(
         when (left.toInt()) {
-            in 203..212 -> ((left - 202u).toString() + this.takeLast(6).toInt().toString()).toUInt()
-            in 480..488 -> ((left - 469u).toString() + this.takeLast(6).toInt().toString()).toUInt()
-            in 2100..2146 -> ((left.toString().take(3).toUInt() - 208u).toString() + this.takeLast(7).toInt().toString()).toUInt()
-            in 2010..2099 -> ((left - 1943u).toString() + this.takeLast(6).toInt().toString()).toUInt()
-            in 2147..2199 -> ((left.toString().take(3).toUInt() - 199u).toString() + this.takeLast(7).toInt().toString()).toUInt()
-            in 4100..4199 -> ((left.toString().take(3).toUInt() - 389u).toString() + this.takeLast(7).toInt().toString()).toUInt()
-            in 3800..3989 -> ((left.toString().take(3).toUInt() - 349u).toString() + this.takeLast(7).toInt().toString()).toUInt()
+            in 203..212 -> ((left - 202).toString() + this.takeLast(6).toInt().toString()).toLong()
+            in 480..488 -> ((left - 469).toString() + this.takeLast(6).toInt().toString()).toLong()
+            in 2100..2146 -> ((left.toString().take(3).toLong() - 208).toString() + this.takeLast(7).toInt().toString()).toLong()
+            in 2010..2099 -> ((left - 1943).toString() + this.takeLast(6).toInt().toString()).toLong()
+            in 2147..2199 -> ((left.toString().take(3).toLong() - 199).toString() + this.takeLast(7).toInt().toString()).toLong()
+            in 4100..4199 -> ((left.toString().take(3).toLong() - 389).toString() + this.takeLast(7).toInt().toString()).toLong()
+            in 3800..3989 -> ((left.toString().take(3).toLong() - 349).toString() + this.takeLast(7).toInt().toString()).toLong()
             else -> value
         }
     )

+ 3 - 6
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt

@@ -4,12 +4,9 @@ package net.mamoe.mirai.contact
 
 import kotlinx.coroutines.CoroutineScope
 import net.mamoe.mirai.Bot
-import net.mamoe.mirai.contact.data.Profile
-import net.mamoe.mirai.network.BotSession
-import net.mamoe.mirai.network.protocol.timpc.packet.action.AvatarLink
-import net.mamoe.mirai.network.protocol.timpc.packet.action.FriendNameRemark
-import net.mamoe.mirai.network.protocol.timpc.packet.action.PreviousNameList
-import net.mamoe.mirai.utils.MiraiExperimentalAPI
+import net.mamoe.mirai.network.data.FriendNameRemark
+import net.mamoe.mirai.network.data.PreviousNameList
+import net.mamoe.mirai.network.data.Profile
 
 /**
  * QQ 对象.

+ 8 - 21
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt

@@ -6,11 +6,10 @@ import net.mamoe.mirai.Bot
 import net.mamoe.mirai.contact.isAdministrator
 import net.mamoe.mirai.contact.isOperator
 import net.mamoe.mirai.contact.isOwner
-import net.mamoe.mirai.message.Message
-import net.mamoe.mirai.message.any
-import net.mamoe.mirai.network.protocol.timpc.packet.event.FriendMessage
-import net.mamoe.mirai.network.protocol.timpc.packet.event.GroupMessage
-import net.mamoe.mirai.network.protocol.timpc.packet.event.MessagePacket
+import net.mamoe.mirai.message.FriendMessage
+import net.mamoe.mirai.message.GroupMessage
+import net.mamoe.mirai.message.MessagePacket
+import net.mamoe.mirai.message.data.Message
 import kotlin.contracts.ExperimentalContracts
 import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
@@ -188,15 +187,9 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>(
      * 如果是这个人发的消息, 就执行 [onEvent]. 消息可以是好友消息也可以是群消息
      */
     @MessageDsl
-    suspend inline fun sentBy(qqId: UInt, noinline onEvent: @MessageDsl suspend T.(String) -> Unit) =
+    suspend inline fun sentBy(qqId: Long, noinline onEvent: @MessageDsl suspend T.(String) -> Unit) =
         content({ sender.id == qqId }, onEvent)
 
-    /**
-     * 如果是这个人发的消息, 就执行 [onEvent]. 消息可以是好友消息也可以是群消息
-     */
-    @MessageDsl
-    suspend inline fun sentBy(qqId: Long, noinline onEvent: @MessageDsl suspend T.(String) -> Unit) = sentBy(qqId.toUInt(), onEvent)
-
     /**
      * 如果是管理员或群主发的消息, 就执行 [onEvent]
      */
@@ -222,21 +215,15 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>(
      * 如果是来自这个群的消息, 就执行 [onEvent]
      */
     @MessageDsl
-    suspend inline fun sentFrom(id: UInt, noinline onEvent: @MessageDsl suspend T.(String) -> Unit) =
+    suspend inline fun sentFrom(id: Long, noinline onEvent: @MessageDsl suspend T.(String) -> Unit) =
         content({ if (this is GroupMessage) group.id == id else false }, onEvent)
 
-    /**
-     * 如果是来自这个群的消息, 就执行 [onEvent]
-     */
-    @MessageDsl
-    suspend inline fun sentFrom(id: Long, noinline onEvent: @MessageDsl suspend T.(String) -> Unit) = sentFrom(id.toUInt(), onEvent)
-
     /**
      * 如果消息内容包含 [M] 类型的 [Message], 就执行 [onEvent]
      */
     @MessageDsl
     suspend inline fun <reified M : Message> has(noinline onEvent: @MessageDsl suspend T.(String) -> Unit) =
-        subscriber { if (message.any<M>()) onEvent(this) }
+        subscriber { if (message.any { it::class == M::class }) onEvent(this) }
 
     /**
      * 如果 [filter] 返回 `true` 就执行 `onEvent`
@@ -283,7 +270,7 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>(
      */
     @MessageDsl
     suspend inline fun Regex.matchingReply(noinline replier: AnyReplier<T>) {
-        content({ [email protected](it) != null }){
+        content({ [email protected](it) != null }) {
             @Suppress("DSL_SCOPE_VIOLATION_WARNING") // false negative warning
             executeAndReply(replier)
         }

+ 22 - 24
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/SubscribersWithBot.kt

@@ -7,7 +7,6 @@ import net.mamoe.mirai.event.events.BotEvent
 import net.mamoe.mirai.event.internal.HandlerWithSession
 import net.mamoe.mirai.event.internal.Listener
 import net.mamoe.mirai.event.internal.subscribeInternal
-import net.mamoe.mirai.network.BotSession
 import kotlin.reflect.KClass
 
 /**
@@ -19,88 +18,87 @@ import kotlin.reflect.KClass
 
 // region 顶层方法
 
-suspend inline fun <reified E : BotEvent> Bot.subscribe(noinline handler: suspend BotSession.(E) -> ListeningStatus): Listener<E> =
+suspend inline fun <reified E : BotEvent> Bot.subscribe(noinline handler: suspend Bot.(E) -> ListeningStatus): Listener<E> =
     E::class.subscribe(this, handler)
 
-suspend inline fun <reified E : BotEvent> Bot.subscribeAlways(noinline listener: suspend BotSession.(E) -> Unit): Listener<E> =
+suspend inline fun <reified E : BotEvent> Bot.subscribeAlways(noinline listener: suspend Bot.(E) -> Unit): Listener<E> =
     E::class.subscribeAlways(this, listener)
 
-suspend inline fun <reified E : BotEvent> Bot.subscribeOnce(noinline listener: suspend BotSession.(E) -> Unit): Listener<E> =
+suspend inline fun <reified E : BotEvent> Bot.subscribeOnce(noinline listener: suspend Bot.(E) -> Unit): Listener<E> =
     E::class.subscribeOnce(this, listener)
 
-suspend inline fun <reified E : BotEvent, T> Bot.subscribeUntil(valueIfStop: T, noinline listener: suspend BotSession.(E) -> T): Listener<E> =
+suspend inline fun <reified E : BotEvent, T> Bot.subscribeUntil(valueIfStop: T, noinline listener: suspend Bot.(E) -> T): Listener<E> =
     E::class.subscribeUntil(this, valueIfStop, listener)
 
-suspend inline fun <reified E : BotEvent> Bot.subscribeUntilFalse(noinline listener: suspend BotSession.(E) -> Boolean): Listener<E> =
+suspend inline fun <reified E : BotEvent> Bot.subscribeUntilFalse(noinline listener: suspend Bot.(E) -> Boolean): Listener<E> =
     E::class.subscribeUntilFalse(this, listener)
 
-suspend inline fun <reified E : BotEvent> Bot.subscribeUntilTrue(noinline listener: suspend BotSession.(E) -> Boolean): Listener<E> =
+suspend inline fun <reified E : BotEvent> Bot.subscribeUntilTrue(noinline listener: suspend Bot.(E) -> Boolean): Listener<E> =
     E::class.subscribeUntilTrue(this, listener)
 
-suspend inline fun <reified E : BotEvent> Bot.subscribeUntilNull(noinline listener: suspend BotSession.(E) -> Any?): Listener<E> =
+suspend inline fun <reified E : BotEvent> Bot.subscribeUntilNull(noinline listener: suspend Bot.(E) -> Any?): Listener<E> =
     E::class.subscribeUntilNull(this, listener)
 
 
-suspend inline fun <reified E : BotEvent, T> Bot.subscribeWhile(valueIfContinue: T, noinline listener: suspend BotSession.(E) -> T): Listener<E> =
+suspend inline fun <reified E : BotEvent, T> Bot.subscribeWhile(valueIfContinue: T, noinline listener: suspend Bot.(E) -> T): Listener<E> =
     E::class.subscribeWhile(this, valueIfContinue, listener)
 
-suspend inline fun <reified E : BotEvent> Bot.subscribeWhileFalse(noinline listener: suspend BotSession.(E) -> Boolean): Listener<E> =
+suspend inline fun <reified E : BotEvent> Bot.subscribeWhileFalse(noinline listener: suspend Bot.(E) -> Boolean): Listener<E> =
     E::class.subscribeWhileFalse(this, listener)
 
-suspend inline fun <reified E : BotEvent> Bot.subscribeWhileTrue(noinline listener: suspend BotSession.(E) -> Boolean): Listener<E> =
+suspend inline fun <reified E : BotEvent> Bot.subscribeWhileTrue(noinline listener: suspend Bot.(E) -> Boolean): Listener<E> =
     E::class.subscribeWhileTrue(this, listener)
 
-suspend inline fun <reified E : BotEvent> Bot.subscribeWhileNull(noinline listener: suspend BotSession.(E) -> Any?): Listener<E> =
+suspend inline fun <reified E : BotEvent> Bot.subscribeWhileNull(noinline listener: suspend Bot.(E) -> Any?): Listener<E> =
     E::class.subscribeWhileNull(this, listener)
 
 // endregion
 
-
 // region KClass 的扩展方法 (仅内部使用)
 
 @PublishedApi
-internal suspend fun <E : BotEvent> KClass<E>.subscribe(bot: Bot, handler: suspend BotSession.(E) -> ListeningStatus) =
+internal suspend fun <E : BotEvent> KClass<E>.subscribe(bot: Bot, handler: suspend Bot.(E) -> ListeningStatus) =
     this.subscribeInternal(HandlerWithSession(bot, handler))
 
 @PublishedApi
-internal suspend fun <E : BotEvent> KClass<E>.subscribeAlways(bot: Bot, listener: suspend BotSession.(E) -> Unit) =
+internal suspend fun <E : BotEvent> KClass<E>.subscribeAlways(bot: Bot, listener: suspend Bot.(E) -> Unit) =
     this.subscribeInternal(HandlerWithSession(bot) { listener(it); ListeningStatus.LISTENING })
 
 @PublishedApi
-internal suspend fun <E : BotEvent> KClass<E>.subscribeOnce(bot: Bot, listener: suspend BotSession.(E) -> Unit) =
+internal suspend fun <E : BotEvent> KClass<E>.subscribeOnce(bot: Bot, listener: suspend Bot.(E) -> Unit) =
     this.subscribeInternal(HandlerWithSession(bot) { listener(it); ListeningStatus.STOPPED })
 
 @PublishedApi
-internal suspend fun <E : BotEvent, T> KClass<E>.subscribeUntil(bot: Bot, valueIfStop: T, listener: suspend BotSession.(E) -> T) =
+internal suspend fun <E : BotEvent, T> KClass<E>.subscribeUntil(bot: Bot, valueIfStop: T, listener: suspend Bot.(E) -> T) =
     subscribeInternal(HandlerWithSession(bot) { if (listener(it) === valueIfStop) ListeningStatus.STOPPED else ListeningStatus.LISTENING })
 
 @PublishedApi
-internal suspend inline fun <E : BotEvent> KClass<E>.subscribeUntilFalse(bot: Bot, noinline listener: suspend BotSession.(E) -> Boolean) =
+internal suspend inline fun <E : BotEvent> KClass<E>.subscribeUntilFalse(bot: Bot, noinline listener: suspend Bot.(E) -> Boolean) =
     subscribeUntil(bot, false, listener)
 
 @PublishedApi
-internal suspend inline fun <E : BotEvent> KClass<E>.subscribeUntilTrue(bot: Bot, noinline listener: suspend BotSession.(E) -> Boolean) =
+internal suspend inline fun <E : BotEvent> KClass<E>.subscribeUntilTrue(bot: Bot, noinline listener: suspend Bot.(E) -> Boolean) =
     subscribeUntil(bot, true, listener)
 
 @PublishedApi
-internal suspend inline fun <E : BotEvent> KClass<E>.subscribeUntilNull(bot: Bot, noinline listener: suspend BotSession.(E) -> Any?) =
+internal suspend inline fun <E : BotEvent> KClass<E>.subscribeUntilNull(bot: Bot, noinline listener: suspend Bot.(E) -> Any?) =
     subscribeUntil(bot, null, listener)
 
 
 @PublishedApi
-internal suspend fun <E : BotEvent, T> KClass<E>.subscribeWhile(bot: Bot, valueIfContinue: T, listener: suspend BotSession.(E) -> T) =
+internal suspend fun <E : BotEvent, T> KClass<E>.subscribeWhile(bot: Bot, valueIfContinue: T, listener: suspend Bot.(E) -> T) =
     subscribeInternal(HandlerWithSession(bot) { if (listener(it) !== valueIfContinue) ListeningStatus.STOPPED else ListeningStatus.LISTENING })
 
 @PublishedApi
-internal suspend inline fun <E : BotEvent> KClass<E>.subscribeWhileFalse(bot: Bot, noinline listener: suspend BotSession.(E) -> Boolean) =
+internal suspend inline fun <E : BotEvent> KClass<E>.subscribeWhileFalse(bot: Bot, noinline listener: suspend Bot.(E) -> Boolean) =
     subscribeWhile(bot, false, listener)
 
 @PublishedApi
-internal suspend inline fun <E : BotEvent> KClass<E>.subscribeWhileTrue(bot: Bot, noinline listener: suspend BotSession.(E) -> Boolean) =
+internal suspend inline fun <E : BotEvent> KClass<E>.subscribeWhileTrue(bot: Bot, noinline listener: suspend Bot.(E) -> Boolean) =
     subscribeWhile(bot, true, listener)
 
 @PublishedApi
-internal suspend inline fun <E : BotEvent> KClass<E>.subscribeWhileNull(bot: Bot, noinline listener: suspend BotSession.(E) -> Any?) =
+internal suspend inline fun <E : BotEvent> KClass<E>.subscribeWhileNull(bot: Bot, noinline listener: suspend Bot.(E) -> Any?) =
     subscribeWhile(bot, null, listener)
 
 // endregion

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

@@ -0,0 +1,10 @@
+package net.mamoe.mirai.event.events
+
+import net.mamoe.mirai.network.data.EventPacket
+
+/**
+ * 被挤下线. 只能获取到中文的消息
+ */
+inline class ConnectionOccupiedEvent(val message: String) : EventPacket {
+    override fun toString(): String = "ConnectionOccupiedEvent(${message.replace("\n", "")})"
+}

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

@@ -0,0 +1,10 @@
+package net.mamoe.mirai.event.events
+
+import net.mamoe.mirai.contact.QQ
+import net.mamoe.mirai.network.data.EventPacket
+import net.mamoe.mirai.utils.OnlineStatus
+
+data class FriendStatusChanged(
+    val qq: QQ,
+    val status: OnlineStatus
+) : EventPacket

+ 75 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/MuteEvent.kt

@@ -0,0 +1,75 @@
+package net.mamoe.mirai.event.events
+
+import net.mamoe.mirai.contact.Group
+import net.mamoe.mirai.contact.Member
+import net.mamoe.mirai.network.data.EventPacket
+
+
+// region mute
+/**
+ * 某群成员被禁言事件
+ */
+@Suppress("unused", "MemberVisibilityCanBePrivate")
+class MemberMuteEvent(
+    val member: Member,
+    override val durationSeconds: Int,
+    override val operator: Member
+) : MuteEvent() {
+    override val group: Group get() = operator.group
+    override fun toString(): String = "MemberMuteEvent(member=${member.id}, group=${group.id}, operator=${operator.id}, duration=${durationSeconds}s"
+}
+
+/**
+ * 机器人被禁言事件
+ */
+class BeingMutedEvent(
+    override val durationSeconds: Int,
+    override val operator: Member
+) : MuteEvent() {
+    override val group: Group get() = operator.group
+    override fun toString(): String = "BeingMutedEvent(group=${group.id}, operator=${operator.id}, duration=${durationSeconds}s"
+}
+
+sealed class MuteEvent : EventOfMute() {
+    abstract override val operator: Member
+    abstract override val group: Group
+    abstract val durationSeconds: Int
+}
+// endregion
+
+// region unmute
+/**
+ * 某群成员被解除禁言事件
+ */
+@Suppress("unused")
+class MemberUnmuteEvent(
+    val member: Member,
+    override val operator: Member
+) : UnmuteEvent() {
+    override val group: Group get() = operator.group
+    override fun toString(): String = "MemberUnmuteEvent(member=${member.id}, group=${group.id}, operator=${operator.id}"
+}
+
+/**
+ * 机器人被解除禁言事件
+ */
+@Suppress("SpellCheckingInspection")
+class BeingUnmutedEvent(
+    override val operator: Member
+) : UnmuteEvent() {
+    override val group: Group get() = operator.group
+    override fun toString(): String = "BeingUnmutedEvent(group=${group.id}, operator=${operator.id}"
+}
+
+sealed class UnmuteEvent : EventOfMute() {
+    abstract override val operator: Member
+    abstract override val group: Group
+}
+
+// endregion
+
+abstract class EventOfMute : EventPacket {
+    abstract val operator: Member
+    abstract val group: Group
+}
+

+ 0 - 48
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/PacketEvents.kt

@@ -1,48 +0,0 @@
-package net.mamoe.mirai.event.events
-
-import net.mamoe.mirai.Bot
-import net.mamoe.mirai.event.Cancellable
-import net.mamoe.mirai.network.protocol.timpc.packet.OutgoingPacket
-import net.mamoe.mirai.network.protocol.timpc.packet.Packet
-
-/* Abstract */
-
-/**
- * 数据包相关事件
- */
-internal sealed class PacketEvent<P : Packet>(bot: Bot, open val packet: P) : BotEvent(bot)
-
-
-/* Client to Server */
-
-/**
- * 发送给服务器的数据包的相关事件
- */
-internal sealed class OutgoingPacketEvent(bot: Bot, packet: OutgoingPacket) : PacketEvent<OutgoingPacket>(bot, packet)
-
-/**
- * 包已发送, 此时包数据已完全发送至服务器, 且包已被关闭.
- *
- * 不可被取消
- */
-internal class PacketSentEvent(bot: Bot, packet: OutgoingPacket) : OutgoingPacketEvent(bot, packet)
-
-/**
- * 包发送前, 此时包数据已经编码完成.
- *
- * 可被取消
- */
-internal class BeforePacketSendEvent(bot: Bot, packet: OutgoingPacket) : OutgoingPacketEvent(bot, packet), Cancellable
-
-
-/* Server to Client */
-
-/**
- * 来自服务器的数据包的相关事件
- */
-internal sealed class ServerPacketEvent<P : Packet>(bot: Bot, packet: P) : PacketEvent<P>(bot, packet)
-
-/**
- * 服务器数据包接收事件. 此时包已经解密完成.
- */
-internal class ServerPacketReceivedEvent<P : Packet>(bot: Bot, packet: P) : ServerPacketEvent<P>(bot, packet), Cancellable

+ 27 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/ReceiveFriendAddRequestEvent.kt

@@ -0,0 +1,27 @@
+package net.mamoe.mirai.event.events
+
+import net.mamoe.mirai.contact.QQ
+import net.mamoe.mirai.network.data.EventPacket
+import net.mamoe.mirai.utils.unsafeWeakRef
+import kotlin.jvm.JvmOverloads
+
+/**
+ * 陌生人请求添加机器人账号为好友
+ */
+class ReceiveFriendAddRequestEvent(
+    _qq: QQ,
+    /**
+     * 验证消息
+     */
+    val message: String
+) : EventPacket {
+    val qq: QQ by _qq.unsafeWeakRef()
+
+    /**
+     * 同意这个请求
+     *
+     * @param remark 备注名, 不设置则需为 `null`
+     */
+    @JvmOverloads // TODO: 2019/12/17 协议抽象
+    suspend fun approve(remark: String? = null): Unit = qq.bot.approveFriendAddRequest(qq.id, remark)
+}

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

@@ -1,17 +1,17 @@
 package net.mamoe.mirai.event.internal
 
-import kotlinx.coroutines.*
+import kotlinx.coroutines.CompletableJob
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.sync.Mutex
 import kotlinx.coroutines.sync.withLock
+import kotlinx.coroutines.withContext
 import net.mamoe.mirai.Bot
-import net.mamoe.mirai.event.EventDebugLogger
 import net.mamoe.mirai.event.ListeningStatus
 import net.mamoe.mirai.event.Subscribable
 import net.mamoe.mirai.event.events.BotEvent
-import net.mamoe.mirai.network.BotSession
-import net.mamoe.mirai.network.session
-import net.mamoe.mirai.utils.internal.inlinedRemoveIf
+import net.mamoe.mirai.utils.LockFreeLinkedList
 import net.mamoe.mirai.utils.io.logStacktrace
+import net.mamoe.mirai.utils.unsafeWeakRef
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.coroutineContext
 import kotlin.jvm.JvmField
@@ -24,8 +24,6 @@ import kotlin.reflect.KFunction
  */
 var EventDisabled = false
 
-// TODO: 2019/11/29 修改监听为 lock-free 模式
-
 /**
  * 监听和广播实现.
  * 它会首先检查这个事件是否正在被广播
@@ -34,38 +32,9 @@ var EventDisabled = false
  *
  * @author Him188moe
  */ // inline to avoid a Continuation creation
-internal suspend inline fun <L : Listener<E>, E : Subscribable> KClass<E>.subscribeInternal(listener: L): L = with(this.listeners()) {
-    if (mainMutex.tryLock(listener)) {//能锁则代表这个事件目前没有正在广播.
-        try {
-            add(listener)//直接修改主监听者列表
-            EventDebugLogger.debug("Added a listener to ${[email protected]}")
-        } finally {
-            mainMutex.unlock(listener)
-        }
-        return listener
-    }
-
-    //不能锁住, 则这个事件正在广播, 那么要将新的监听者放入缓存
-    cacheMutex.withLock {
-        cache.add(listener)
-        EventDebugLogger.debug("Added a listener to cache of ${[email protected]}")
-    }
-
-    GlobalScope.launch {
-        //启动协程并等待正在进行的广播结束, 然后将缓存转移到主监听者列表
-        //启动后的协程马上就会因为锁而被挂起
-        mainMutex.withLock(listener) {
-            cacheMutex.withLock {
-                if (cache.size != 0) {
-                    addAll(cache)
-                    cache.clear()
-                    EventDebugLogger.debug("Cache of ${[email protected]} is now transferred to main")
-                }
-            }
-        }
-    }
-
-    return@with listener
+internal suspend inline fun <L : Listener<E>, E : Subscribable> KClass<E>.subscribeInternal(listener: L): L {
+    this.listeners().addLast(listener)
+    return listener
 }
 
 /**
@@ -77,8 +46,6 @@ internal suspend inline fun <L : Listener<E>, E : Subscribable> KClass<E>.subscr
  * @author Him188moe
  */
 sealed class Listener<in E : Subscribable> : CompletableJob {
-    @JvmField
-    internal val lock = Mutex()
     abstract suspend fun onEvent(event: E): ListeningStatus
 }
 
@@ -113,7 +80,7 @@ internal class Handler<in E : Subscribable>
 @Suppress("FunctionName")
 internal suspend inline fun <E : Subscribable> HandlerWithSession(
     bot: Bot,
-    noinline handler: suspend BotSession.(E) -> ListeningStatus
+    noinline handler: suspend Bot.(E) -> ListeningStatus
 ): HandlerWithSession<E> {
     return HandlerWithSession(bot, coroutineContext[Job], coroutineContext, handler)
 }
@@ -125,10 +92,10 @@ internal suspend inline fun <E : Subscribable> HandlerWithSession(
  */
 @PublishedApi
 internal class HandlerWithSession<E : Subscribable> @PublishedApi internal constructor(
-    @JvmField val bot: Bot,
-    parentJob: Job?, private val context: CoroutineContext, @JvmField val handler: suspend BotSession.(E) -> ListeningStatus
-) :
-    Listener<E>(), CompletableJob by Job(parentJob) {
+    bot: Bot,
+    parentJob: Job?, private val context: CoroutineContext, @JvmField val handler: suspend Bot.(E) -> ListeningStatus
+) : Listener<E>(), CompletableJob by Job(parentJob) {
+    val bot: Bot by bot.unsafeWeakRef()
 
     override suspend fun onEvent(event: E): ListeningStatus {
         if (isCompleted || isCancelled) return ListeningStatus.STOPPED
@@ -137,10 +104,11 @@ internal class HandlerWithSession<E : Subscribable> @PublishedApi internal const
         if (event !is BotEvent || event.bot !== bot) return ListeningStatus.LISTENING
 
         return try {
-            withContext(context) { bot.session.handler(event) }.also { if (it == ListeningStatus.STOPPED) complete() }
+            withContext(context) { bot.handler(event) }.also { if (it == ListeningStatus.STOPPED) complete() }
         } catch (e: Throwable) {
             e.logStacktrace()
             //completeExceptionally(e)
+            complete()
             ListeningStatus.STOPPED
         }
     }
@@ -151,24 +119,7 @@ internal class HandlerWithSession<E : Subscribable> @PublishedApi internal const
  */
 internal suspend fun <E : Subscribable> KClass<out E>.listeners(): EventListeners<E> = EventListenerManger.get(this)
 
-internal class EventListeners<E : Subscribable> : MutableList<Listener<E>> by mutableListOf() {
-    /**
-     * 主监听者列表.
-     * 广播事件时使用这个锁.
-     */
-    @JvmField
-    val mainMutex = Mutex()
-    /**
-     * 缓存(监听)事件时使用的锁
-     */
-    @JvmField
-    val cacheMutex = Mutex()
-    /**
-     * 等待加入到主 list 的监听者. 务必使用 [cacheMutex]
-     */
-    @JvmField
-    val cache: MutableList<Listener<E>> = mutableListOf()
-
+internal class EventListeners<E : Subscribable> : LockFreeLinkedList<Listener<E>>() {
     init {
         this::class.members.filterIsInstance<KFunction<*>>().forEach {
             if (it.name == "add") {
@@ -198,51 +149,26 @@ internal object EventListenerManger {
 
 }
 
-internal suspend fun <E : Subscribable> E.broadcastInternal(): E {
+// inline: NO extra Continuation
+internal suspend inline fun <E : Subscribable> E.broadcastInternal(): E {
     if (EventDisabled) return this
 
-    callListeners(this::class.listeners())
-
-    applySuperListeners(this::class) { callListeners(it) }
-    return this
-}
+    callAndRemoveIfRequired(this::class.listeners())
 
-private suspend inline fun <E : Subscribable> E.callListeners(listeners: EventListeners<in E>) {
-    //自己持有, 则是在一个事件中
-    if (listeners.mainMutex.holdsLock(listeners)) {
-        callAndRemoveIfRequired(listeners)
-    } else {
-        while (!listeners.mainMutex.tryLock(listeners)) {
-            delay(10)
-        }
-        try {
-            callAndRemoveIfRequired(listeners)
-        } finally {
-            listeners.mainMutex.unlock(listeners)
+    this::class.supertypes.forEach { superType ->
+        if (Subscribable::class.isInstance(superType)) {
+            // the super type is a child of Subscribable, then we can cast.
+            @Suppress("UNCHECKED_CAST")
+            callAndRemoveIfRequired((superType.classifier as KClass<out Subscribable>).listeners())
         }
     }
+    return this
 }
 
-private suspend inline fun <E : Subscribable> E.callAndRemoveIfRequired(listeners: EventListeners<in E>) = listeners.inlinedRemoveIf {
-    if (it.lock.tryLock()) {
-        try {
-            it.onEvent(this) == ListeningStatus.STOPPED
-        } finally {
-            it.lock.unlock()
+private suspend inline fun <E : Subscribable> E.callAndRemoveIfRequired(listeners: EventListeners<E>) {
+    listeners.forEach {
+        if (it.onEvent(this) == ListeningStatus.STOPPED) {
+            listeners.remove(it) // atomic remove
         }
-    } else false
-}
-
-/**
- * apply [block] to all the [EventListeners] in [clazz]'s superclasses
- */
-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 Subscribable>>().firstOrNull() ?: return
-    @Suppress("UNCHECKED_CAST")
-    block(superEventClass.listeners() as EventListeners<in E>)
-    applySuperListeners(superEventClass, block)
+    }
 }

+ 0 - 11
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Face.kt

@@ -1,11 +0,0 @@
-package net.mamoe.mirai.message
-
-/**
- * QQ 自带表情
- */
-inline class Face(val id: FaceId) : Message {
-    override val stringValue: String get() = "[face${id.value}]"
-    override fun toString(): String = stringValue
-
-    companion object Key : Message.Key<Face>
-}

+ 26 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/FriendMessage.kt

@@ -0,0 +1,26 @@
+package net.mamoe.mirai.message
+
+import net.mamoe.mirai.Bot
+import net.mamoe.mirai.contact.QQ
+import net.mamoe.mirai.event.BroadcastControllable
+import net.mamoe.mirai.message.data.MessageChain
+import net.mamoe.mirai.utils.MiraiInternalAPI
+
+class FriendMessage(
+    bot: Bot,
+    /**
+     * 是否是在这次登录之前的消息, 即消息记录
+     */
+    val previous: Boolean,
+    override val sender: QQ,
+    override val message: MessageChain
+) : MessagePacket<QQ, QQ>(bot), BroadcastControllable {
+    /**
+     * 是否应被自动广播. 此为内部 API
+     */
+    @MiraiInternalAPI
+    override val shouldBroadcast: Boolean
+        get() = !previous
+
+    override val subject: QQ get() = sender
+}

+ 35 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt

@@ -0,0 +1,35 @@
+package net.mamoe.mirai.message
+
+import net.mamoe.mirai.Bot
+import net.mamoe.mirai.contact.Group
+import net.mamoe.mirai.contact.Member
+import net.mamoe.mirai.contact.MemberPermission
+import net.mamoe.mirai.message.data.At
+import net.mamoe.mirai.message.data.MessageChain
+import net.mamoe.mirai.utils.unsafeWeakRef
+
+@Suppress("unused", "NOTHING_TO_INLINE")
+class GroupMessage(
+    bot: Bot,
+    group: Group,
+    val senderName: String,
+    /**
+     * 发送方权限.
+     */
+    val permission: MemberPermission,
+    sender: Member,
+    override val message: MessageChain
+) : MessagePacket<Member, Group>(bot) {
+    val group: Group by group.unsafeWeakRef()
+    override val sender: Member by sender.unsafeWeakRef()
+
+    /*
+    01 00 09 01 00 06 66 61 69 6C 65 64 19 00 45 01 00 42 AA 02 3F 08 06 50 02 60 00 68 00 88 01 00 9A 01 31 08 0A 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 98 03 00 A0 03 02 B0 03 00 C0 03 00 D0 03 00 E8 03 02 8A 04 04 08 02 08 01 90 04 80 C8 10 0E 00 0E 01 00 04 00 00 08 E4 07 00 04 00 00 00 01 12 00 1E 02 00 09 E9 85 B1 E9 87 8E E6 98 9F 03 00 01 02 05 00 04 00 00 00 03 08 00 04 00 00 00 04
+     */
+    override val subject: Group get() = group
+
+    inline fun At.member(): Member = group.getMember(this.target)
+    inline fun Long.member(): Member = group.getMember(this)
+    override fun toString(): String =
+        "GroupMessage(group=${group.id}, senderName=$senderName, sender=${sender.id}, permission=${permission.name}, message=$message)"
+}

+ 91 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt

@@ -0,0 +1,91 @@
+@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
+
+package net.mamoe.mirai.message
+
+import kotlinx.io.core.ByteReadPacket
+import net.mamoe.mirai.Bot
+import net.mamoe.mirai.contact.*
+import net.mamoe.mirai.event.events.BotEvent
+import net.mamoe.mirai.message.data.*
+import net.mamoe.mirai.network.data.EventPacket
+import net.mamoe.mirai.network.data.ImageLink
+import net.mamoe.mirai.utils.*
+import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
+import kotlin.jvm.JvmName
+
+/**
+ * 平台相关扩展
+ */
+@UseExperimental(MiraiInternalAPI::class)
+expect abstract class MessagePacket<TSender : QQ, TSubject : Contact>(bot: Bot) : MessagePacketBase<TSender, TSubject>
+
+@MiraiInternalAPI
+abstract class MessagePacketBase<TSender : QQ, TSubject : Contact>(_bot: Bot) : EventPacket, BotEvent() {
+    override val bot: Bot by _bot.unsafeWeakRef()
+
+    /**
+     * 消息事件主体.
+     *
+     * 对于好友消息, 这个属性为 [QQ] 的实例;
+     * 对于群消息, 这个属性为 [Group] 的实例
+     *
+     * 在回复消息时, 可通过 [subject] 作为回复对象
+     */
+    abstract val subject: TSubject
+
+    /**
+     * 发送人
+     */
+    abstract val sender: TSender
+
+    abstract val message: MessageChain
+
+
+    // region Send to subject
+
+    /**
+     * 给这个消息事件的主体发送消息
+     * 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息
+     * 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息
+     */
+    suspend inline fun reply(message: MessageChain) = subject.sendMessage(message)
+
+    suspend inline fun reply(message: Message) = subject.sendMessage(message.chain())
+    suspend inline fun reply(plain: String) = subject.sendMessage(plain.toMessage())
+
+    @JvmName("reply1")
+    suspend inline fun String.reply() = reply(this)
+
+    @JvmName("reply1")
+    suspend inline fun Message.reply() = reply(this)
+
+    @JvmName("reply1")
+    suspend inline fun MessageChain.reply() = reply(this)
+
+    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 Image download
+    suspend inline fun Image.getLink(): ImageLink = with(bot) { getLink() }
+
+    suspend inline fun Image.downloadAsByteArray(): ByteArray = getLink().downloadAsByteArray()
+    suspend inline fun Image.download(): ByteReadPacket = getLink().download()
+    // endregion
+
+    fun At.qq(): QQ = bot.getQQ(this.target)
+
+    fun Int.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0).toLong())
+    fun Long.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0))
+
+    suspend inline fun Int.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0).toLong())
+    suspend inline fun Long.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0))
+    suspend inline fun GroupId.group(): Group = bot.getGroup(this)
+    suspend inline fun GroupInternalId.group(): Group = bot.getGroup(this)
+}

+ 2 - 2
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/At.kt → mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/At.kt

@@ -1,6 +1,6 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE")
 
-package net.mamoe.mirai.message
+package net.mamoe.mirai.message.data
 
 import net.mamoe.mirai.contact.QQ
 
@@ -8,7 +8,7 @@ import net.mamoe.mirai.contact.QQ
 /**
  * At 一个人. 只能发送给一个群.
  */
-inline class At(val target: UInt) : Message {
+inline class At(val target: Long) : Message {
     constructor(target: QQ) : this(target.id)
 
     override val stringValue: String get() = "[@$target]" // TODO: 2019/11/25 使用群名称进行 at. 因为手机端只会显示这个文字

+ 13 - 4
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/FaceID.kt → mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Face.kt

@@ -1,14 +1,23 @@
-@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
-
-package net.mamoe.mirai.message
+package net.mamoe.mirai.message.data
 
 import kotlin.jvm.JvmStatic
 
+/**
+ * QQ 自带表情
+ */
+inline class Face(val id: FaceId) : Message {
+    override val stringValue: String get() = "[face${id.value}]"
+    override fun toString(): String = stringValue
+
+    companion object Key : Message.Key<Face>
+}
+
 /**
  * @author LamGC
  */
 @Suppress("SpellCheckingInspection", "unused")
-inline class FaceId(inline val value: UByte) {
+@UseExperimental(ExperimentalUnsignedTypes::class)
+inline class FaceId constructor(inline val value: UByte) {
     companion object {
         @JvmStatic
         val unknown: FaceId = FaceId(0xffu)

+ 7 - 5
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Image.kt → mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt

@@ -1,10 +1,9 @@
 @file:Suppress("EXPERIMENTAL_API_USAGE")
 
-package net.mamoe.mirai.message
+package net.mamoe.mirai.message.data
 
 import net.mamoe.mirai.contact.Contact
 import net.mamoe.mirai.contact.sendMessage
-import net.mamoe.mirai.network.protocol.timpc.packet.action.FriendImagePacket
 import net.mamoe.mirai.utils.ExternalImage
 
 
@@ -30,7 +29,8 @@ inline class ImageId0x06(override inline val value: String) : ImageId {
 /**
  * 一般是群的图片的 id.
  */
-class ImageId0x03 constructor(override inline val value: String, inline val uniqueId: UInt, inline val height: Int, inline val width: Int) : ImageId {
+class ImageId0x03 constructor(override inline val value: String, inline val uniqueId: UInt, inline val height: Int, inline val width: Int) :
+    ImageId {
     override fun toString(): String = "ImageId(value=$value, uniqueId=${uniqueId}, height=$height, width=$width)"
 
     val md5: ByteArray
@@ -46,7 +46,8 @@ class ImageId0x03 constructor(override inline val value: String, inline val uniq
 inline fun ImageId(value: String): ImageId = ImageId0x06(value)
 
 @Suppress("FunctionName", "NOTHING_TO_INLINE")
-inline fun ImageId(value: String, uniqueId: UInt, height: Int, width: Int): ImageId = ImageId0x03(value, uniqueId, height, width)
+inline fun ImageId(value: String, uniqueId: UInt, height: Int, width: Int): ImageId =
+    ImageId0x03(value, uniqueId, height, width)
 
 
 /**
@@ -65,6 +66,7 @@ fun ImageId.checkLength() = check(value.length == 37 || value.length == 42) { "I
 fun ImageId.requireLength() = require(value.length == 37 || value.length == 42) { "Illegal ImageId length" }
 
 @Suppress("NOTHING_TO_INLINE")
-inline fun ImageId.image(): Image = Image(this)
+inline fun ImageId.image(): Image =
+    Image(this)
 
 suspend inline fun ImageId.sendTo(contact: Contact) = contact.sendMessage(this.image())

+ 1 - 1
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Message.kt → mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt

@@ -1,6 +1,6 @@
 @file:Suppress("MemberVisibilityCanBePrivate", "unused", "EXPERIMENTAL_API_USAGE")
 
-package net.mamoe.mirai.message
+package net.mamoe.mirai.message.data
 
 import net.mamoe.mirai.contact.Contact
 import net.mamoe.mirai.contact.sendMessage

+ 42 - 43
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageChain.kt → mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt

@@ -1,5 +1,6 @@
-package net.mamoe.mirai.message
+package net.mamoe.mirai.message.data
 
+import net.mamoe.mirai.message.data.NullMessageChain.toString
 import net.mamoe.mirai.utils.MiraiExperimentalAPI
 import kotlin.contracts.ExperimentalContracts
 import kotlin.contracts.contract
@@ -75,7 +76,8 @@ fun MessageChain(vararg messages: Message): MessageChain =
  * 构造 [MessageChain]
  */
 @Suppress("FunctionName")
-fun MessageChain(messages: Iterable<Message>): MessageChain = MessageChainImpl(messages.toMutableList())
+fun MessageChain(messages: Iterable<Message>): MessageChain =
+    MessageChainImpl(messages.toMutableList())
 
 /**
  * 构造单元素的不可修改的 [MessageChain]. 内部类实现为 [SingleMessageChain]
@@ -106,13 +108,16 @@ fun SingleMessageChain(delegate: Message): MessageChain {
  * 否则将调用 [MessageChain] 构造一个 [MessageChainImpl]
  */
 @Suppress("NOTHING_TO_INLINE")
-inline fun Message.chain(): MessageChain = if (this is MessageChain) this else MessageChain(this)
+inline fun Message.chain(): MessageChain = if (this is MessageChain) this else MessageChain(
+    this
+)
 
 /**
  * 构造 [MessageChain]
  */
 @Suppress("unused", "NOTHING_TO_INLINE")
-inline fun List<Message>.messageChain(): MessageChain = MessageChain(this)
+inline fun List<Message>.messageChain(): MessageChain =
+    MessageChain(this)
 
 
 /**
@@ -230,51 +235,45 @@ class EmptyMessageChain : MessageChain {
  * Null 的 [MessageChain].
  * 它不包含任何元素, 也没有创建任何 list.
  *
- * - 所有 get 方法均抛出 [IndexOutOfBoundsException]
- * - 所有 add 方法均抛出 [UnsupportedOperationException]
- * - 其他判断类方法均 false 或 -1
+ * 除 [toString] 外, 其他方法均 [error]
  */
 object NullMessageChain : MessageChain {
-    override fun subList(fromIndex: Int, toIndex: Int): MutableList<Message> = unsupported()
+    override fun subList(fromIndex: Int, toIndex: Int): MutableList<Message> = error("accessing NullMessageChain")
 
-    override val stringValue: String
-        get() = ""
+    override val stringValue: String get() = "null"
 
-    override fun toString(): String = stringValue
+    override fun toString(): String = "null"
 
-    override fun contains(sub: String): Boolean = false
-    override fun contains(element: Message): Boolean = false
-    override fun followedBy(tail: Message): MessageChain =
-        MessageChainImpl(tail)
-
-    override val size: Int = 0
-    override fun containsAll(elements: Collection<Message>): Boolean = false
-    override fun get(index: Int): Message = throw IndexOutOfBoundsException()
-    override fun indexOf(element: Message): Int = -1
-    override fun isEmpty(): Boolean = true
-    override fun iterator(): MutableIterator<Message> =
-        EmptyMutableIterator()
-
-    override fun lastIndexOf(element: Message): Int = -1
-    override fun add(element: Message): Boolean = unsupported()
-    override fun add(index: Int, element: Message) = throw IndexOutOfBoundsException(index.toString())
-    override fun addAll(index: Int, elements: Collection<Message>): Boolean =
-        throw IndexOutOfBoundsException(index.toString())
-
-    override fun addAll(elements: Collection<Message>): Boolean = unsupported()
-    override fun clear() {}
-    override fun listIterator(): MutableListIterator<Message> =
-        EmptyMutableListIterator()
+    override fun contains(sub: String): Boolean = error("accessing NullMessageChain")
+    override fun contains(element: Message): Boolean = error("accessing NullMessageChain")
+    override fun followedBy(tail: Message): MessageChain = error("accessing NullMessageChain")
 
-    override fun listIterator(index: Int): MutableListIterator<Message> =
-        throw IndexOutOfBoundsException(index.toString())
-
-    override fun remove(element: Message): Boolean = false
-    override fun removeAll(elements: Collection<Message>): Boolean = false
-    override fun removeAt(index: Int): Message = throw IndexOutOfBoundsException(index.toString())
-    override fun retainAll(elements: Collection<Message>): Boolean = false
-    override fun set(index: Int, element: Message): Message = throw IndexOutOfBoundsException(index.toString())
-    private fun unsupported(): Nothing = throw UnsupportedOperationException()
+    override val size: Int get() = error("accessing NullMessageChain")
+    override fun containsAll(elements: Collection<Message>): Boolean = error("accessing NullMessageChain")
+    override fun get(index: Int): Message = error("accessing NullMessageChain")
+    override fun indexOf(element: Message): Int = error("accessing NullMessageChain")
+    override fun isEmpty(): Boolean = error("accessing NullMessageChain")
+    override fun iterator(): MutableIterator<Message> = error("accessing NullMessageChain")
+
+    override fun lastIndexOf(element: Message): Int = error("accessing NullMessageChain")
+    override fun add(element: Message): Boolean = error("accessing NullMessageChain")
+    override fun add(index: Int, element: Message) = error("accessing NullMessageChain")
+    override fun addAll(index: Int, elements: Collection<Message>): Boolean = error("accessing NullMessageChain")
+
+    override fun addAll(elements: Collection<Message>): Boolean = error("accessing NullMessageChain")
+    override fun clear() {
+        error("accessing NullMessageChain")
+    }
+
+    override fun listIterator(): MutableListIterator<Message> = error("accessing NullMessageChain")
+
+    override fun listIterator(index: Int): MutableListIterator<Message> = error("accessing NullMessageChain")
+
+    override fun remove(element: Message): Boolean = error("accessing NullMessageChain")
+    override fun removeAll(elements: Collection<Message>): Boolean = error("accessing NullMessageChain")
+    override fun removeAt(index: Int): Message = error("accessing NullMessageChain")
+    override fun retainAll(elements: Collection<Message>): Boolean = error("accessing NullMessageChain")
+    override fun set(index: Int, element: Message): Message = error("accessing NullMessageChain")
 }
 
 /**

+ 1 - 1
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/PlainText.kt → mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt

@@ -1,4 +1,4 @@
-package net.mamoe.mirai.message
+package net.mamoe.mirai.message.data
 
 
 inline class PlainText(override val stringValue: String) : Message {

+ 5 - 3
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/XML.kt → mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/XML.kt

@@ -1,13 +1,14 @@
 @file:Suppress("MemberVisibilityCanBePrivate")
 
-package net.mamoe.mirai.message
+package net.mamoe.mirai.message.data
 
 /**
  * XML 消息, 如分享, 卡片等.
  *
  * @see buildXMLMessage
  */
-inline class XMLMessage(override val stringValue: String) : Message, SingleOnly {
+inline class XMLMessage(override val stringValue: String) : Message,
+    SingleOnly {
     override fun followedBy(tail: Message): Nothing = error("XMLMessage Message cannot be followed")
     override fun toString(): String = stringValue
 }
@@ -16,7 +17,8 @@ inline class XMLMessage(override val stringValue: String) : Message, SingleOnly
  * 构造一条 XML 消息
  */
 @XMLDsl
-inline fun buildXMLMessage(block: @XMLDsl XMLMessageBuilder.() -> Unit): XMLMessage = XMLMessage(XMLMessageBuilder().apply(block).text)
+inline fun buildXMLMessage(block: @XMLDsl XMLMessageBuilder.() -> Unit): XMLMessage =
+    XMLMessage(XMLMessageBuilder().apply(block).text)
 
 @Suppress("NOTHING_TO_INLINE")
 @XMLDsl

+ 8 - 7
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/internal/MessageDataInternal.kt

@@ -3,7 +3,8 @@
 package net.mamoe.mirai.message.internal
 
 import kotlinx.io.core.*
-import net.mamoe.mirai.message.*
+import net.mamoe.mirai.message.MessageType
+import net.mamoe.mirai.message.data.*
 import net.mamoe.mirai.utils.io.*
 import net.mamoe.mirai.utils.unzip
 
@@ -27,7 +28,7 @@ internal fun IoBuffer.parsePlainTextOrAt(): Message {
         PlainText(msg)
     } else {
         discardExact(10)
-        At(readUInt())
+        At(readQQ())
     }
 }
 
@@ -38,13 +39,13 @@ internal fun IoBuffer.parseLongText0x19(): PlainText {
     //AA 02 33 50 00 60 00 68 00 9A 01 2A 08 09 20 CB 50 78 80 80 04 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 D3 02 A0 03 10 B0 03 00 C0 03 AF 9C 01 D0 03 00 E8 03 00
     //AA 02 30 50 00 60 00 68 00 9A 01 27 08 0A 78 A7 C0 04 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 00 B0 03 00 C0 03 00 D0 03 00 E8 03 00
     // 应该是手机发送时的字体或气泡之类的
-    // println("parseLongText0x19.raw=${raw.toUHexString()}")
+    // println("parseLongText0x19.raw=${raw.toHexString()}")
     return PlainText("")
 }
 
 internal fun IoBuffer.parseMessageImage0x06(): Image {
     discardExact(1)
-    //MiraiLogger.debug(this.toUHexString())
+    //MiraiLogger.debug(this.toHexString())
     val filenameLength = readShort()
 
     discardExact(filenameLength.toInt())
@@ -199,7 +200,7 @@ internal fun ByteReadPacket.readMessage(): Message? {
     }
 }
 
-internal fun ByteReadPacket.readMessageChain(): MessageChain {
+fun ByteReadPacket.readMessageChain(): MessageChain {
     val chain = MessageChain()
     do {
         if (this.remaining == 0L) {
@@ -209,7 +210,7 @@ internal fun ByteReadPacket.readMessageChain(): MessageChain {
     return chain
 }
 
-internal fun MessageChain.toPacket(): ByteReadPacket = buildPacket {
+fun MessageChain.toPacket(): ByteReadPacket = buildPacket {
     [email protected] { message ->
         writePacket(with(message) {
             when (this) {
@@ -244,7 +245,7 @@ internal fun MessageChain.toPacket(): ByteReadPacket = buildPacket {
                         writeShortLVString(stringValue) // 这个应该是 "@群名", 手机上面会显示这个消息, 电脑会显示下面那个
                         // 06 00 0D 00 01 00 00 00 08 00 76 E4 B8 DD 00 00
                         writeHex("06 00 0D 00 01 00 00 00 08 00")
-                        writeUInt(target)
+                        writeQQ(target)
                         writeZero(2)
                     }
                 }

+ 23 - 17
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt

@@ -1,17 +1,11 @@
+@file:Suppress("EXPERIMENTAL_API_USAGE")
+
 package net.mamoe.mirai.network
 
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CompletableJob
 import kotlinx.coroutines.CoroutineScope
 import net.mamoe.mirai.Bot
-import net.mamoe.mirai.network.protocol.timpc.handler.DataPacketSocketAdapter
-import net.mamoe.mirai.network.protocol.timpc.handler.TemporaryPacketHandler
-import net.mamoe.mirai.network.protocol.timpc.packet.OutgoingPacket
-import net.mamoe.mirai.network.protocol.timpc.packet.Packet
-import net.mamoe.mirai.network.protocol.timpc.packet.login.HeartbeatPacket
-import net.mamoe.mirai.network.protocol.timpc.packet.login.RequestSKeyPacket
-import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult
-import net.mamoe.mirai.utils.MiraiInternalAPI
 import net.mamoe.mirai.utils.io.PlatformDatagramChannel
 
 /**
@@ -33,29 +27,41 @@ import net.mamoe.mirai.utils.io.PlatformDatagramChannel
  * A BotNetworkHandler is used to connect with Tencent servers.
  */
 @Suppress("PropertyName")
-interface BotNetworkHandler<Socket : DataPacketSocketAdapter> : CoroutineScope {
-    val socket: Socket
-    val bot: Bot
-
-    val supervisor: CompletableJob
+abstract class BotNetworkHandler : CoroutineScope {
+    abstract val bot: Bot
 
-    val session: BotSession
+    abstract val supervisor: CompletableJob
 
     /**
      * 依次尝试登录到可用的服务器. 在任一服务器登录完成后返回登录结果
      * 本函数将挂起直到登录成功.
      */
-    suspend fun login(): LoginResult
+    abstract suspend fun login()
 
     /**
      * 等待直到与服务器断开连接. 若未连接则立即返回
      */
-    suspend fun awaitDisconnection()
+    abstract suspend fun awaitDisconnection()
 
     /**
      * 关闭网络接口, 停止所有有关协程和任务
      */
-    fun close(cause: Throwable? = null) {
+    open fun close(cause: Throwable? = null) {
         supervisor.cancel(CancellationException("handler closed", cause))
     }
+
+/*
+    @PublishedApi
+    internal abstract fun CoroutineScope.QQ(bot: Bot, id: Long, coroutineContext: CoroutineContext): QQ
+
+    @PublishedApi
+    internal abstract fun CoroutineScope.Group(bot: Bot, groupId: GroupId, info: RawGroupInfo, context: CoroutineContext): Group
+
+    @PublishedApi
+    internal abstract fun Group.Member(delegate: QQ, permission: MemberPermission, coroutineContext: CoroutineContext): Member
+
+
+ */
+
+
 }

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません