Jelajahi Sumber

Make Profile data class

Him188 6 tahun lalu
induk
melakukan
c655c0fe33

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

@@ -65,7 +65,7 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) : CoroutineScope {
 
     val contacts = ContactSystem()
 
-    var network: BotNetworkHandler<*> = TIMBotNetworkHandler(this)
+    var network: BotNetworkHandler<*> = TIMBotNetworkHandler(this.coroutineContext, this)
 
     init {
         launch {
@@ -90,7 +90,7 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) : CoroutineScope {
         } catch (e: Exception) {
             logger.error(e)
         }
-        network = TIMBotNetworkHandler(this)
+        network = TIMBotNetworkHandler(this.coroutineContext, this)
         return network.login(configuration)
     }
 

+ 38 - 77
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt

@@ -3,6 +3,7 @@
 package net.mamoe.mirai.contact
 
 import com.soywiz.klock.Date
+import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.Deferred
 import net.mamoe.mirai.Bot
 import net.mamoe.mirai.message.Message
@@ -17,6 +18,7 @@ import net.mamoe.mirai.network.sessionKey
 import net.mamoe.mirai.qqAccount
 import net.mamoe.mirai.sendPacket
 import net.mamoe.mirai.utils.SuspendLazy
+import net.mamoe.mirai.utils.internal.PositiveNumbers
 import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
 import net.mamoe.mirai.withSession
 
@@ -33,7 +35,9 @@ class ContactList<C : Contact> : MutableMap<UInt, C> by mutableMapOf()
 sealed class Contact(val bot: Bot, val id: UInt) {
 
     /**
-     * 向这个对象发送消息. 速度太快会被服务器拒绝(无响应)
+     * 向这个对象发送消息.
+     *
+     * 速度太快会被服务器拒绝(无响应). 在测试中不延迟地发送 6 条消息就会被屏蔽之后的数据包 1 秒左右.
      */
     abstract suspend fun sendMessage(message: MessageChain)
 
@@ -65,7 +69,7 @@ fun UInt.groupId(): GroupId = GroupId(this)
  *
  * 注: 在 Java 中常用 [Long] 来表示 [UInt]
  */
-fun Long.groupId(): GroupId = GroupId(this.coerceAtLeastOrFail(0).toUInt())
+fun @receiver:PositiveNumbers Long.groupId(): GroupId = GroupId(this.coerceAtLeastOrFail(0).toUInt())
 
 /**
  * 一些群 API 使用的 ID. 在使用时会特别注明
@@ -95,9 +99,7 @@ class Group internal constructor(bot: Bot, val groupId: GroupId) : Contact(bot,
         bot.sendPacket(SendGroupMessagePacket(bot.qqAccount, internalId, bot.sessionKey, message))
     }
 
-    override fun toString(): String {
-        return "Group(${this.id})"
-    }
+    override fun toString(): String = "Group(${this.id})"
 
     companion object
 }
@@ -111,6 +113,9 @@ inline fun <R> Contact.withSession(block: BotSession.() -> R): R = bot.withSessi
 /**
  * QQ 对象.
  * 注意: 一个 [QQ] 实例并不是独立的, 它属于一个 [Bot].
+ * 它不能被直接构造. 任何时候都应从 [Bot.qq], [Bot.ContactSystem.getQQ], [BotSession.qq] 或事件中获取.
+ *
+ * 对于同一个 [Bot] 任何一个人的 [QQ] 实例都是单一的.
  *
  * A QQ instance helps you to receive event from or sendPacket event to.
  * Notice that, one QQ instance belong to one [Bot], that is, QQ instances from different [Bot] are NOT the same.
@@ -118,12 +123,17 @@ inline fun <R> Contact.withSession(block: BotSession.() -> R): R = bot.withSessi
  * @author Him188moe
  */
 open class QQ internal constructor(bot: Bot, id: UInt) : Contact(bot, id) {
-    // TODO: 2019/11/8 should be suspend val if kotlin supports
-    val profile: Deferred<Profile> by bot.network.SuspendLazy { updateProfile() }
+    private var _profile: Profile? = null
+    private val _initialProfile by bot.network.SuspendLazy { updateProfile() }
 
-    override suspend fun sendMessage(message: MessageChain) {
+    /**
+     * 用户资料.
+     */
+    val profile: Deferred<Profile>
+        get() = if (_profile == null) _initialProfile else CompletableDeferred(_profile!!)
+
+    override suspend fun sendMessage(message: MessageChain) =
         bot.sendPacket(SendFriendMessagePacket(bot.qqAccount, id, bot.sessionKey, message))
-    }
 
     /**
      * 更新个人资料.
@@ -137,19 +147,13 @@ open class QQ internal constructor(bot: Bot, id: UInt) : Contact(bot, id) {
      * ```
      */
     suspend fun updateProfile(): Profile = bot.withSession {
-        RequestProfileDetailsPacket(bot.qqAccount, id, sessionKey)
-            .sendAndExpectAsync<RequestProfileDetailsResponse, Profile> { it.profile }
-            .await().let {
-                @Suppress("UNCHECKED_CAST")
-                if ((::profile as SuspendLazy<Profile>).isInitialized()) {
-                    profile.await().apply { copyFrom(it) }
-                } else it
-            }
-    }
+        _profile = RequestProfileDetailsPacket(bot.qqAccount, id, sessionKey)
+            .sendAndExpect<RequestProfileDetailsResponse, Profile> { it.profile }
 
-    override fun toString(): String {
-        return "QQ(${this.id})"
+        return _profile!!
     }
+
+    override fun toString(): String = "QQ(${this.id})"
 }
 
 
@@ -161,9 +165,7 @@ class Member internal constructor(bot: Bot, id: UInt, val group: Group) : QQ(bot
         TODO("Group member implementation")
     }
 
-    override fun toString(): String {
-        return "Member(${this.id})"
-    }
+    override fun toString(): String = "Member(${this.id})"
 }
 
 /**
@@ -187,61 +189,20 @@ enum class MemberPermission {
 /**
  * 个人资料
  */
-// FIXME: 2019/11/8 should be `data class Profile`
 @Suppress("PropertyName")
-class Profile(
-    internal var _qq: UInt,
-    internal var _nickname: String,
-    internal var _zipCode: String?,
-    internal var _phone: String?,
-    internal var _gender: Gender,
-    internal var _birthday: Date?,
-    internal var _personalStatus: String?,
-    internal var _school: String?,
-    internal var _homepage: String?,
-    internal var _email: String?,
-    internal var _company: String?
-) {
-
-    val qq: UInt get() = _qq
-    val nickname: String get() = _nickname
-    val zipCode: String? get() = _zipCode
-    val phone: String? get() = _phone
-    val gender: Gender get() = _gender
-    /**
-     * 个性签名
-     */
-    val personalStatus: String? get() = _personalStatus
-    val school: String? get() = _school
-    val company: String? get() = _company
-
-    /**
-     * 主页
-     */
-    val homepage: String? get() = _homepage
-    val email: String? get() = _email
-    val birthday: Date? get() = _birthday
-
-    override fun toString(): String = "Profile(" +
-            "qq=$qq, nickname=$nickname, zipCode=$zipCode, phone=$phone, " +
-            "gender=$gender, birthday=$birthday, personalStatus=$personalStatus, school=$school, " +
-            "homepage=$homepage, email=$email, company=$company" +
-            ")"
-}
-
-fun Profile.copyFrom(another: Profile) {
-    this._qq = another.qq
-    this._nickname = another.nickname
-    this._zipCode = another.zipCode
-    this._phone = another.phone
-    this._gender = another.gender
-    this._birthday = another.birthday
-    this._personalStatus = another.personalStatus
-    this._school = another.school
-    this._homepage = another.homepage
-    this._email = another.email
-    this._company = another.company
-}
+data class Profile(
+    val qq: UInt,
+    val nickname: String,
+    val zipCode: String?,
+    val phone: String?,
+    val gender: Gender,
+    val birthday: Date?,
+    val personalStatus: String?,
+    val school: String?,
+    val homepage: String?,
+    val email: String?,
+    val company: String?
+)
 
 /**
  * 性别

+ 9 - 3
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageType.kt

@@ -8,10 +8,16 @@ enum class MessageType(val value: UByte) {
     PLAIN_TEXT(0x01u),
     AT(0x06u),
     FACE(0x02u),
-    GROUP_IMAGE(0x03u),
-    FRIEND_IMAGE(0x06u),
+    /**
+     * [ImageId.value] 长度为 42 的图片
+     */
+    IMAGE_42(0x03u),
+    /**
+     * [ImageId.value] 长度为 37 的图片
+     */
+    IMAGE_37(0x06u),
     ;
 
 
-    val intValue: Int = this.value.toInt()
+    inline val intValue: Int get() = this.value.toInt()
 }

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

@@ -145,7 +145,7 @@ fun ByteReadPacket.readMessageChain(): MessageChain {
     return chain
 }
 
-fun MessageChain.toPacket(forGroup: Boolean): ByteReadPacket = buildPacket {
+fun MessageChain.toPacket(): ByteReadPacket = buildPacket {
     [email protected] { message ->
         writePacket(with(message) {
             when (this) {
@@ -170,7 +170,7 @@ fun MessageChain.toPacket(forGroup: Boolean): ByteReadPacket = buildPacket {
                     when (id.value.length) {
                         //   "{F61593B5-5B98-1798-3F47-2A91D32ED2FC}.jpg"
                         42 -> {
-                            writeUByte(MessageType.GROUP_IMAGE.value)
+                            writeUByte(MessageType.IMAGE_42.value)
 
                             //00 00 03 00 CB 02 00 2A 7B 46 36 31 35 39 33 42 35 2D 35 42 39 38 2D 31 37 39 38 2D 33 46 34 37 2D 32 41 39 31 44 33 32 45 44 32 46 43 7D 2E 6A 70 67
                             // 04 00 04 87 E5 09 3B 05 00 04 D2 C4 C0 B7 06 00 04 00 00 00 50 07 00 01 43 08 00 00 09 00 01 01 0B 00 00 14 00 04 00 00 00 00 15 00 04 00 00 01 ED 16 00 04 00 00 02 17 18 00 04 00 00 EB 34 FF 00 5C 15 36 20 39 32 6B 41 31 43 38 37 65 35 30 39 33 62 64 32 63 34 63 30 62 37 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
@@ -191,7 +191,7 @@ fun MessageChain.toPacket(forGroup: Boolean): ByteReadPacket = buildPacket {
 
                         //   "/01ee6426-5ff1-4cf0-8278-e8634d2909ef"
                         37 -> {
-                            writeUByte(MessageType.FRIEND_IMAGE.value)
+                            writeUByte(MessageType.IMAGE_37.value)
 
                             // 00 00 06 00 F3 02
                             // 00 1B 24 5B 56 54 4A 38 60 4C 5A 4E 46 7D 53 39 4F 52 36 25 45 60 42 55 53 2E 6A 70 67

+ 3 - 3
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotSession.kt

@@ -25,7 +25,6 @@ import net.mamoe.mirai.utils.getGTK
 import net.mamoe.mirai.utils.internal.PositiveNumbers
 import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
 import kotlin.coroutines.coroutineContext
-import kotlin.jvm.JvmField
 
 /**
  * 构造 [BotSession] 的捷径
@@ -60,7 +59,6 @@ class BotSession(
      */
     val sKey: String get() = _sKey
 
-    @JvmField
     @Suppress("PropertyName")
     internal var _sKey: String = ""
         set(value) {
@@ -73,7 +71,6 @@ class BotSession(
      */
     val gtk: Int get() = _gtk
 
-    @JvmField
     private var _gtk: Int = 0
 
     /**
@@ -139,6 +136,9 @@ class BotSession(
      * ```
      * @sample Bot.addFriend 添加好友
      */
+    suspend inline fun <reified P : Packet, R> OutgoingPacket.sendAndExpect(checkSequence: Boolean = true, crossinline block: (P) -> R): R =
+        sendAndExpectAsync<P, R>(checkSequence) { block(it) }.await()
+
     suspend inline fun <reified P : Packet> OutgoingPacket.sendAndExpect(checkSequence: Boolean = true): P =
         sendAndExpectAsync<P, P>(checkSequence) { it }.await()
 

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

@@ -38,11 +38,11 @@ internal expect val NetworkDispatcher: CoroutineDispatcher
  *
  * @see BotNetworkHandler
  */
-internal class TIMBotNetworkHandler internal constructor(override inline val bot: Bot) :
+internal class TIMBotNetworkHandler internal constructor(coroutineContext: CoroutineContext, override inline val bot: Bot) :
     BotNetworkHandler<TIMBotNetworkHandler.BotSocketAdapter>, PacketHandlerList() {
 
     override val coroutineContext: CoroutineContext =
-        NetworkDispatcher + CoroutineExceptionHandler { _, e ->
+        coroutineContext + NetworkDispatcher + CoroutineExceptionHandler { _, e ->
             bot.logger.error("An exception was thrown in a coroutine under TIMBotNetworkHandler", e)
         } + SupervisorJob()
 

+ 1 - 1
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/handler/TemporaryPacketHandler.kt

@@ -29,7 +29,7 @@ class TemporaryPacketHandler<P : Packet, R>(
     private val fromSession: BotSession,
     private val checkSequence: Boolean,
     /**
-     * 调用者的 [CoroutineContext]
+     * 调用者的 [CoroutineContext]. 包处理过程将会在这个 context 下运行
      */
     private val callerContext: CoroutineContext
 ) {

+ 11 - 11
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/Profile.kt

@@ -61,23 +61,23 @@ object RequestProfileDetailsPacket : SessionPacketFactory<RequestProfileDetailsR
         discardExact(6)
         val map = readTLVMap(tagSize = 2, expectingEOF = true)
         val profile = Profile(
-            _qq = qq,
-            _nickname = map[0x4E22u]?.stringOfWitch() ?: "",//error("Cannot determine nickname")
-            _zipCode = map[0x4E25u]?.stringOfWitch(),
-            _phone = map[0x4E27u]?.stringOfWitch(),
-            _gender = when (map[0x4E29u]?.let { it[0] }?.toUInt()) {
+            qq = qq,
+            nickname = map[0x4E22u]?.stringOfWitch() ?: "",//error("Cannot determine nickname")
+            zipCode = map[0x4E25u]?.stringOfWitch(),
+            phone = map[0x4E27u]?.stringOfWitch(),
+            gender = when (map[0x4E29u]?.let { it[0] }?.toUInt()) {
                 null -> Gender.SECRET //error("Cannot determine gender, entry 0x4E29u not found")
                 0x02u -> Gender.FEMALE
                 0x01u -> Gender.MALE
                 else -> Gender.SECRET // 猜的
                 //else -> error("Cannot determine gender, bad value of 0x4E29u: ${map[0x4729u]!![0].toUHexString()}")
             },
-            _birthday = map[0x4E3Fu]?.let { Date(it.toUInt().toInt()) },
-            _personalStatus = map[0x4E33u]?.stringOfWitch(),
-            _homepage = map[0x4E2Du]?.stringOfWitch(),
-            _company = map[0x5DC8u]?.stringOfWitch(),
-            _school = map[0x4E35u]?.stringOfWitch(),
-            _email = map[0x4E2Bu]?.stringOfWitch()
+            birthday = map[0x4E3Fu]?.let { Date(it.toUInt().toInt()) },
+            personalStatus = map[0x4E33u]?.stringOfWitch(),
+            homepage = map[0x4E2Du]?.stringOfWitch(),
+            company = map[0x5DC8u]?.stringOfWitch(),
+            school = map[0x4E35u]?.stringOfWitch(),
+            email = map[0x4E2Bu]?.stringOfWitch()
         )
         map.clear()
 

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

@@ -52,7 +52,7 @@ object SendFriendMessagePacket : SessionPacketFactory<SendFriendMessagePacket.Re
         writeHex(TIMProtocol.messageConstNewest)
         writeZero(2)
 
-        writePacket(message.toPacket(false))
+        writePacket(message.toPacket())
 
         /*
             //Plain text

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

@@ -35,7 +35,7 @@ object SendGroupMessagePacket : SessionPacketFactory<SendGroupMessagePacket.Resp
                 writeHex(TIMProtocol.messageConst1)
                 writeZero(2)
 
-                writePacket(message.toPacket(true))
+                writePacket(message.toPacket())
             }
             /*it.writeByte(0x01)
             it.writeShort(bytes.size + 3)

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

@@ -27,7 +27,7 @@ fun <R> CoroutineScope.SuspendLazy(initializer: suspend () -> R): Lazy<Deferred<
  * @sample QQ.profile
  */
 @PublishedApi
-internal class SuspendLazy<R>(scope: CoroutineScope, val initializer: suspend () -> R) : Lazy<Deferred<R>> {
+internal class SuspendLazy<R>(scope: CoroutineScope, initializer: suspend () -> R) : Lazy<Deferred<R>> {
     private val valueUpdater: Deferred<R> by lazy { scope.async { initializer() } }
 
     @Suppress("EXPERIMENTAL_API_USAGE")

+ 19 - 10
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/NumberUtils.kt

@@ -1,30 +1,39 @@
 package net.mamoe.mirai.utils.internal
 
+/**
+ * 要求 [this] 最小为 [min].
+ */
 @PublishedApi
-internal fun Int.coerceAtLeastOrFail(value: Int): Int {
-    require(this > value)
+internal fun Int.coerceAtLeastOrFail(min: Int): Int {
+    require(this >= min)
     return this
 }
 
+/**
+ * 要求 [this] 最小为 [min].
+ */
 @PublishedApi
-internal fun Long.coerceAtLeastOrFail(value: Long): Long {
-    require(this > value)
+internal fun Long.coerceAtLeastOrFail(min: Long): Long {
+    require(this >= min)
     return this
 }
 
+/**
+ * 要求 [this] 最大为 [max].
+ */
 @PublishedApi
-internal fun Int.coerceAtMostOrFail(maximumValue: Int): Int =
-    if (this > maximumValue) error("value is greater than its expected maximum value $maximumValue")
+internal fun Int.coerceAtMostOrFail(max: Int): Int =
+    if (this >= max) error("value is greater than its expected maximum value $max")
     else this
 
 @PublishedApi
-internal fun Long.coerceAtMostOrFail(maximumValue: Long): Long =
-    if (this > maximumValue) error("value is greater than its expected maximum value $maximumValue")
+internal fun Long.coerceAtMostOrFail(max: Long): Long =
+    if (this >= max) error("value is greater than its expected maximum value $max")
     else this
 
 /**
- * 表示这个参数必须为正数
+ * 表示这个参数必须为正数. 仅用于警示
  */
 @Retention(AnnotationRetention.SOURCE)
-@Target(AnnotationTarget.VALUE_PARAMETER)
+@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.TYPE)
 internal annotation class PositiveNumbers