Him188 преди 5 години
родител
ревизия
7d5063653a
променени са 20 файла, в които са добавени 362 реда и са изтрити 293 реда
  1. 1 1
      mirai-core-api/src/commonMain/kotlin/IMirai.kt
  2. 1 1
      mirai-core-api/src/commonMain/kotlin/message/MessageEvent.kt
  3. 2 2
      mirai-core-api/src/commonMain/kotlin/message/data/CustomMessage.kt
  4. 3 1
      mirai-core-api/src/commonMain/kotlin/message/data/ForwardMessage.kt
  5. 23 16
      mirai-core-api/src/commonMain/kotlin/message/data/HummerMessage.kt
  6. 7 4
      mirai-core-api/src/commonMain/kotlin/message/data/Image.kt
  7. 32 29
      mirai-core-api/src/commonMain/kotlin/message/data/Message.kt
  8. 29 18
      mirai-core-api/src/commonMain/kotlin/message/data/MessageChain.kt
  9. 7 5
      mirai-core-api/src/commonMain/kotlin/message/data/MessageChainBuilder.kt
  10. 24 37
      mirai-core-api/src/commonMain/kotlin/message/data/MessageSource.kt
  11. 4 6
      mirai-core-api/src/commonMain/kotlin/message/data/QuoteReply.kt
  12. 8 17
      mirai-core-api/src/commonMain/kotlin/message/data/RichMessage.kt
  13. 4 8
      mirai-core-api/src/commonMain/kotlin/message/data/Voice.kt
  14. 39 108
      mirai-core-api/src/commonMain/kotlin/message/data/impl.kt
  15. 53 0
      mirai-core-api/src/commonMain/kotlin/net/mamoe/mirai/message/data/MessageKey.kt
  16. 5 6
      mirai-core-api/src/commonTest/kotlin/message.data/ConstrainSingleTest.kt
  17. 107 0
      mirai-core-api/src/commonTest/kotlin/message.data/MessageKeyTest.kt
  18. 2 2
      mirai-core/src/commonMain/kotlin/QQAndroidBot.common.kt
  19. 5 22
      mirai-core/src/commonMain/kotlin/message/imagesImpl.kt
  20. 6 10
      mirai-core/src/jvmTest/kotlin/ContentEqualsTest.kt

+ 1 - 1
mirai-core-api/src/commonMain/kotlin/IMirai.kt

@@ -23,7 +23,7 @@ import net.mamoe.mirai.event.events.NewFriendRequestEvent
 import net.mamoe.mirai.message.MessageReceipt
 import net.mamoe.mirai.message.action.Nudge
 import net.mamoe.mirai.message.data.*
-import net.mamoe.mirai.message.data.Image.Companion.queryUrl
+import net.mamoe.mirai.message.data.Image.Key.queryUrl
 import net.mamoe.mirai.message.data.MessageSource.Key.recall
 import net.mamoe.mirai.utils.MiraiExperimentalApi
 import net.mamoe.mirai.utils.MiraiInternalApi

+ 1 - 1
mirai-core-api/src/commonMain/kotlin/message/MessageEvent.kt

@@ -26,7 +26,7 @@ import net.mamoe.mirai.event.events.BotEvent
 import net.mamoe.mirai.event.subscribe
 import net.mamoe.mirai.internal.network.Packet
 import net.mamoe.mirai.message.data.*
-import net.mamoe.mirai.message.data.Image.Companion.queryUrl
+import net.mamoe.mirai.message.data.Image.Key.queryUrl
 import net.mamoe.mirai.message.data.MessageSource.Key.quote
 import net.mamoe.mirai.utils.ExternalImage
 import net.mamoe.mirai.utils.sendTo

+ 2 - 2
mirai-core-api/src/commonMain/kotlin/message/data/CustomMessage.kt

@@ -59,8 +59,8 @@ public sealed class CustomMessage : SingleMessage {
          * 在发往服务器时使用此名称.
          * 应确保唯一且不变.
          */
-        public final override val typeName: String
-    ) : ConstrainSingle.Key<M> {
+        public val typeName: String
+    ) {
 
         init {
             @Suppress("LeakingThis")

+ 3 - 1
mirai-core-api/src/commonMain/kotlin/message/data/ForwardMessage.kt

@@ -23,6 +23,7 @@ import net.mamoe.mirai.message.MessageEvent
 import net.mamoe.mirai.message.data.ForwardMessage.DisplayStrategy
 import net.mamoe.mirai.utils.MiraiExperimentalApi
 import net.mamoe.mirai.utils.currentTimeSeconds
+import net.mamoe.mirai.utils.safeCast
 
 
 @MiraiExperimentalApi
@@ -174,7 +175,8 @@ public data class ForwardMessage(
         public val message: Message
     }
 
-    public companion object
+    public companion object Key :
+        AbstractPolymorphicMessageKey<MessageContent, ForwardMessage>(MessageContent, { it.safeCast() })
 }
 
 

+ 23 - 16
mirai-core-api/src/commonMain/kotlin/message/data/HummerMessage.kt

@@ -16,8 +16,9 @@ package net.mamoe.mirai.message.data
 import kotlinx.serialization.Contextual
 import kotlinx.serialization.Serializable
 import net.mamoe.mirai.message.code.CodableMessage
-import net.mamoe.mirai.message.data.VipFace.Companion
 import net.mamoe.mirai.message.data.VipFace.Kind
+import net.mamoe.mirai.utils.castOrNull
+import net.mamoe.mirai.utils.safeCast
 
 /**
  * 一些特殊的消息
@@ -26,8 +27,9 @@ import net.mamoe.mirai.message.data.VipFace.Kind
  * @see FlashImage 闪照
  */
 @Serializable
-public sealed class HummerMessage : MessageContent {
-    public companion object
+public sealed class HummerMessage : MessageContent, ConstrainSingle {
+    public companion object Key :
+        AbstractPolymorphicMessageKey<MessageContent, HummerMessage>(MessageContent, { it.castOrNull() })
     // has service type etc.
 }
 
@@ -53,8 +55,12 @@ public data class PokeMessage internal constructor(
     public val type: Int,
     public val id: Int
 ) : HummerMessage(), CodableMessage {
+    override val key: MessageKey<HummerMessage> get() = Key
+
     @Suppress("DEPRECATION_ERROR", "DEPRECATION", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
-    public companion object {
+    public companion object Key :
+        AbstractPolymorphicMessageKey<HummerMessage, PokeMessage>(HummerMessage, { it.castOrNull() }) {
+
         /** 戳一戳 */
         @JvmField
         public val Poke: PokeMessage = PokeMessage("戳一戳", 1, -1)
@@ -174,8 +180,12 @@ public data class VipFace internal constructor(
         }
     }
 
+    override val key: MessageKey<VipFace> get() = Key
+
     @Suppress("DEPRECATION_ERROR", "DEPRECATION", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
-    public companion object {
+    public companion object Key :
+        AbstractPolymorphicMessageKey<HummerMessage, VipFace>(HummerMessage, { it.safeCast() }) {
+
         @JvmStatic
         public val LiuLian: Kind = 9 to "榴莲"
 
@@ -245,12 +255,11 @@ public data class VipFace internal constructor(
  * @see Image 查看图片相关信息
  */
 @Serializable
-public sealed class FlashImage : MessageContent, HummerMessage(), CodableMessage, ConstrainSingle<FlashImage> {
-    override val key: ConstrainSingle.Key<FlashImage> get() = Key
-
-    public companion object Key : ConstrainSingle.Key<FlashImage> {
-        override val typeName: String get() = "FlashImage"
+public sealed class FlashImage : MessageContent, HummerMessage(), CodableMessage, ConstrainSingle {
+    override val key: MessageKey<FlashImage> get() = Key
 
+    public companion object Key :
+        AbstractPolymorphicMessageKey<HummerMessage, FlashImage>(HummerMessage, { it.safeCast() }) {
         /**
          * 将普通图片转换为闪照.
          */
@@ -307,9 +316,8 @@ public inline fun FriendImage.flash(): FriendFlashImage = FlashImage(this) as Fr
  */
 @Serializable
 public data class GroupFlashImage(@Contextual override val image: GroupImage) : FlashImage() {
-    public companion object Key : ConstrainSingle.Key<GroupFlashImage> {
-        public override val typeName: String get() = "GroupFlashImage"
-    }
+    public companion object Key :
+        AbstractPolymorphicMessageKey<FlashImage, GroupFlashImage>(FlashImage, { it.safeCast() })
 }
 
 /**
@@ -317,7 +325,6 @@ public data class GroupFlashImage(@Contextual override val image: GroupImage) :
  */
 @Serializable
 public data class FriendFlashImage(@Contextual override val image: FriendImage) : FlashImage() {
-    public companion object Key : ConstrainSingle.Key<FriendFlashImage> {
-        public override val typeName: String get() = "FriendFlashImage"
-    }
+    public companion object Key :
+        AbstractPolymorphicMessageKey<FlashImage, FriendFlashImage>(FlashImage, { it.safeCast() })
 }

+ 7 - 4
mirai-core-api/src/commonMain/kotlin/message/data/Image.kt

@@ -34,9 +34,10 @@ import net.mamoe.mirai.Bot
 import net.mamoe.mirai.Mirai
 import net.mamoe.mirai.contact.Contact
 import net.mamoe.mirai.message.code.CodableMessage
-import net.mamoe.mirai.message.data.Image.Companion.queryUrl
+import net.mamoe.mirai.message.data.Image.Key.queryUrl
 import net.mamoe.mirai.utils.ExternalImage
 import net.mamoe.mirai.utils.MiraiInternalApi
+import net.mamoe.mirai.utils.safeCast
 import net.mamoe.mirai.utils.sendImage
 import java.io.File
 import java.io.InputStream
@@ -109,7 +110,7 @@ public interface Image : Message, MessageContent, CodableMessage {
         }
     }
 
-    public companion object {
+    public companion object Key : AbstractMessageKey<Image>({ it.safeCast() }) {
         /**
          * 通过 [Image.imageId] 构造一个 [Image] 以便发送.
          * 这个图片必须是服务器已经存在的图片.
@@ -137,7 +138,6 @@ public interface Image : Message, MessageContent, CodableMessage {
         public suspend fun Image.queryUrl(): String {
             val bot = Bot._instances.peekFirst()?.get() ?: error("No Bot available to query image url")
             return Mirai.queryImageUrl(bot, this)
-
         }
     }
 }
@@ -158,7 +158,8 @@ public inline fun Image(imageId: String): Image = Image.fromId(imageId)
 /**
  * 所有 [Image] 实现的基类.
  */
-public abstract class AbstractImage : Image { // make sealed in 1.3.0 ?
+@MiraiInternalApi
+public sealed class AbstractImage : Image { // make sealed in 1.3.0 ?
     private var _stringValue: String? = null
         get() = field ?: kotlin.run {
             field = "[mirai:image:$imageId]"
@@ -185,6 +186,7 @@ public val Image.md5: ByteArray
  * [imageId] 形如 `/f8f1ab55-bf8e-4236-b55e-955848d7069f` (37 长度)  或 `/000000000-3814297509-BFB7027B9354B8F899A062061D74E206` (54 长度)
  */
 // NotOnlineImage
+@MiraiInternalApi
 public abstract class FriendImage @MiraiInternalApi public constructor() :
     AbstractImage() { // change to sealed in the future.
     public companion object
@@ -197,6 +199,7 @@ public abstract class FriendImage @MiraiInternalApi public constructor() :
  * @see Image 查看更多说明
  */
 // CustomFace
+@MiraiInternalApi
 public abstract class GroupImage @MiraiInternalApi public constructor() :
     AbstractImage() { // change to sealed in the future.
     public companion object

+ 32 - 29
mirai-core-api/src/commonMain/kotlin/message/data/Message.kt

@@ -27,10 +27,9 @@ import net.mamoe.mirai.message.MessageEvent
 import net.mamoe.mirai.message.MessageReceipt
 import net.mamoe.mirai.message.MessageSerializer
 import net.mamoe.mirai.message.MessageSerializerImpl
-import net.mamoe.mirai.message.data.ConstrainSingle.Key
 import net.mamoe.mirai.utils.MiraiExperimentalApi
+import net.mamoe.mirai.utils.safeCast
 import kotlin.contracts.contract
-import kotlin.reflect.KClass
 
 /**
  * 可发送的或从服务器接收的消息.
@@ -188,11 +187,17 @@ public interface Message { // must be interface. Don't consider any changes.
         KSerializer<Message> by PolymorphicSerializer(Message::class)
 }
 
+@MiraiExperimentalApi
+@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
+public annotation class ExperimentalMessageKey
+
+
 @MiraiExperimentalApi
 @JvmSynthetic
 public suspend inline operator fun Message.plus(another: Flow<Message>): MessageChain =
     another.fold(this) { acc, it -> acc + it }.asMessageChain()
 
+
 /**
  * [Message.contentToString] 的捷径
  */
@@ -242,7 +247,7 @@ public inline fun Message.isNotPlain(): Boolean {
  */
 // inline: for future removal
 public inline fun Message.repeat(count: Int): MessageChain {
-    if (this is ConstrainSingle<*>) {
+    if (this is ConstrainSingle) {
         // fast-path
         return this.asMessageChain()
     }
@@ -289,36 +294,31 @@ public interface MessageMetadata : SingleMessage {
  *
  * 实现此接口的元素将会在连接时自动处理替换.
  */
-public interface ConstrainSingle<out M : Message> : SingleMessage {
+@ExperimentalMessageKey
+public interface ConstrainSingle : SingleMessage {
     /**
      * 用于判断是否为同一种元素的 [Key]
      * @see Key 查看更多信息
      */
-    public val key: Key<M>
+    @ExperimentalMessageKey
+    public val key: MessageKey<*>
+}
 
-    /**
-     * 类型 Key. 由伴生对象实现, 表示一个 [Message] 对象的类型.
-     *
-     * 除 [MessageChain] 外, 每个 [Message] 类型都拥有一个伴生对象 (companion object) 来持有一个 Key
-     *
-     * 在 [MessageChain.get] 时将会使用到这个 Key 进行判断类型.
-     *
-     * #### 用例
-     * [MessageChain.get][MessageChain.get]: 允许使用数组访问操作符获取指定类型的消息元素
-     * ```
-     * val image: Image = chain[Image]
-     * ```
-     *
-     * @param M 指代持有这个 Key 的消息类型
-     */
-    public interface Key<out M : Message> {
-        /**
-         * 此 [Key] 指代的 [Message] 类型名. 一般为 [KClass.simpleName], 如 "QuoteReply", "PlainText"
-         *
-         * 仅用于提示作用.
-         */
-        public val typeName: String
-    }
+@ExperimentalMessageKey
+public abstract class AbstractMessageKey<out M : SingleMessage>(
+    override val safeCast: (SingleMessage) -> M?,
+) : MessageKey<M> {
+    internal fun tryCast(element: SingleMessage): M? = safeCast(element)
+    internal open fun isSubKey(key: MessageKey<*>): Boolean = key === this
+}
+
+@ExperimentalMessageKey
+public abstract class AbstractPolymorphicMessageKey<out B : SingleMessage, out M : B>(
+    public val baseKey: MessageKey<B>,
+    safeCast: (SingleMessage) -> M?,
+) : MessageKey<M>, AbstractMessageKey<M>(safeCast) {
+    internal val topmostKey: MessageKey<*> =
+        if (baseKey is AbstractPolymorphicMessageKey<*, *>) baseKey.topmostKey else baseKey
 }
 
 /**
@@ -335,7 +335,10 @@ public interface ConstrainSingle<out M : Message> : SingleMessage {
  * @see ForwardMessage 合并转发
  * @see Voice 语音
  */
-public interface MessageContent : SingleMessage
+public interface MessageContent : SingleMessage {
+    @ExperimentalMessageKey
+    public companion object Key : AbstractMessageKey<MessageContent>({ it.safeCast() })
+}
 
 /**
  * 将 [this] 发送给指定联系人

+ 29 - 18
mirai-core-api/src/commonMain/kotlin/message/data/MessageChain.kt

@@ -78,12 +78,17 @@ public interface MessageChain : Message, List<SingleMessage>, RandomAccess {
      * chain.first(At.Key)
      * ```
      *
-     * @param key 由各个类型消息的伴生对象持有. 如 [PlainText.Key]
+     * @param key 由各个类型消息的伴生对象持有. 如 [MessageSource.Key]
      *
      * @see MessageChain.getOrFail 在找不到此类型的元素时抛出 [NoSuchElementException]
      */
-    @JvmName("first")
-    public operator fun <M : Message> get(key: ConstrainSingle.Key<M>): M? = firstOrNull(key)
+    @ExperimentalMessageKey
+    public operator fun <M : SingleMessage> get(key: MessageKey<M>): M? =
+        asSequence().mapNotNull { key.safeCast.invoke(it) }.firstOrNull()
+
+    @ExperimentalMessageKey
+    public operator fun <M : SingleMessage> contains(key: MessageKey<M>): Boolean =
+        asSequence().any { key.safeCast.invoke(it) != null }
 
     public object Serializer : KSerializer<MessageChain> {
         private val delegate = ListSerializer(Message.Serializer)
@@ -98,13 +103,14 @@ public interface MessageChain : Message, List<SingleMessage>, RandomAccess {
 /**
  * 获取第一个类型为 [key] 的 [Message] 实例, 在找不到此类型的元素时抛出 [NoSuchElementException]
  *
- * @param key 由各个类型消息的伴生对象持有. 如 [PlainText.Key]
+ * @param key 由各个类型消息的伴生对象持有. 如 [MessageSource.Key]
  */
+@ExperimentalMessageKey
 @JvmOverloads
-public inline fun <M : Message> MessageChain.getOrFail(
-    key: ConstrainSingle.Key<M>,
-    crossinline lazyMessage: (key: ConstrainSingle.Key<M>) -> String = { key.typeName }
-): M = firstOrNull(key) ?: throw NoSuchElementException(lazyMessage(key))
+public inline fun <M : SingleMessage> MessageChain.getOrFail(
+    key: MessageKey<M>,
+    crossinline lazyMessage: (key: MessageKey<M>) -> String = { key.toString() }
+): M = get(key) ?: throw NoSuchElementException(lazyMessage(key))
 
 
 /**
@@ -153,44 +159,49 @@ public inline fun MessageChain.noneContent(block: (MessageContent) -> Boolean):
  * 获取第一个 [M] 类型的 [Message] 实例
  */
 @JvmSynthetic
-public inline fun <reified M : Message?> MessageChain.firstIsInstanceOrNull(): M? = this.firstOrNull { it is M } as M?
+public inline fun <reified M : SingleMessage?> MessageChain.firstIsInstanceOrNull(): M? =
+    this.firstOrNull { it is M } as M?
 
 /**
  * 获取第一个 [M] 类型的 [Message] 实例
  * @throws [NoSuchElementException] 如果找不到该类型的实例
  */
 @JvmSynthetic
-public inline fun <reified M : Message> MessageChain.firstIsInstance(): M = this.first { it is M } as M
+public inline fun <reified M : SingleMessage> MessageChain.firstIsInstance(): M = this.first { it is M } as M
 
 /**
  * 判断 [this] 中是否存在 [Message] 的实例
  */
 @JvmSynthetic
-public inline fun <reified M : Message> MessageChain.anyIsInstance(): Boolean = this.any { it is M }
-
+public inline fun <reified M : SingleMessage> MessageChain.anyIsInstance(): Boolean = this.any { it is M }
 
 /**
  * 获取第一个 [M] 类型的 [Message] 实例
  */
+@OptIn(ExperimentalMessageKey::class)
+@Deprecated("Use get", ReplaceWith("get(key)"))
 @JvmSynthetic
 @Suppress("UNCHECKED_CAST")
-public fun <M : Message> MessageChain.firstOrNull(key: ConstrainSingle.Key<M>): M? = firstOrNullImpl(key)
+public fun <M : SingleMessage> MessageChain.firstOrNull(key: MessageKey<M>): M? = get(key)
 
 /**
  * 获取第一个 [M] 类型的 [Message] 实例
  * @throws [NoSuchElementException] 如果找不到该类型的实例
  */
+@OptIn(ExperimentalMessageKey::class)
+@Deprecated("Use getOrFail", ReplaceWith("getOrFail(key)", "net.mamoe.mirai.message.data.getOrFail"))
 @JvmSynthetic
 @Suppress("UNCHECKED_CAST")
-public inline fun <M : Message> MessageChain.first(key: ConstrainSingle.Key<M>): M =
-    firstOrNull(key) ?: throw NoSuchElementException("Message type ${key.typeName} not found in chain $this")
+public inline fun <M : SingleMessage> MessageChain.first(key: MessageKey<M>): M =
+    get(key) ?: throw NoSuchElementException("Message type $key not found in chain $this")
 
 /**
  * 获取第一个 [M] 类型的 [Message] 实例
  */
+@ExperimentalMessageKey
 @JvmSynthetic
 @Suppress("UNCHECKED_CAST")
-public inline fun <M : Message> MessageChain.any(key: ConstrainSingle.Key<M>): Boolean = firstOrNull(key) != null
+public inline fun <M : SingleMessage> MessageChain.any(key: MessageKey<M>): Boolean = get(key) != null
 
 // endregion accessors
 
@@ -234,7 +245,7 @@ public inline class OrNullDelegate<out R> @PublishedApi internal constructor(@Jv
  * @see orElse 提供一个不存在则使用默认值的委托
  */
 @JvmSynthetic
-public inline fun <reified T : Message> MessageChain.orNull(): OrNullDelegate<T?> =
+public inline fun <reified T : SingleMessage> MessageChain.orNull(): OrNullDelegate<T?> =
     OrNullDelegate(this.firstIsInstanceOrNull<T>())
 
 /**
@@ -251,7 +262,7 @@ public inline fun <reified T : Message> MessageChain.orNull(): OrNullDelegate<T?
  */
 @Suppress("RemoveExplicitTypeArguments")
 @JvmSynthetic
-public inline fun <reified T : R, R : Message?> MessageChain.orElse(
+public inline fun <reified T : R, R : SingleMessage?> MessageChain.orElse(
     lazyDefault: () -> R
 ): OrNullDelegate<R> = OrNullDelegate<R>(this.firstIsInstanceOrNull<T>() ?: lazyDefault())
 

+ 7 - 5
mirai-core-api/src/commonMain/kotlin/message/data/MessageChainBuilder.kt

@@ -57,7 +57,7 @@ public open class MessageChainBuilder private constructor(
     public final override fun add(element: SingleMessage): Boolean {
         checkBuilt()
         flushCache()
-        return addAndCheckConstrainSingle(element)
+        return container.add(element)
     }
 
     public fun add(element: Message): Boolean {
@@ -65,7 +65,7 @@ public open class MessageChainBuilder private constructor(
         flushCache()
         @Suppress("UNCHECKED_CAST")
         return when (element) {
-            is ConstrainSingle -> addAndCheckConstrainSingle(element)
+            // is ConstrainSingle -> container.add(element)
             is SingleMessage -> container.add(element) // no need to constrain
             is Iterable<*> -> this.addAll(element.flatten())
             else -> error("stub")
@@ -148,7 +148,7 @@ public open class MessageChainBuilder private constructor(
     public fun asMessageChain(): MessageChain {
         built = true
         this.flushCache()
-        return MessageChainImplByCollection(this) // fast-path, no need to constrain
+        return MessageChainImplByCollection(this.constrainSingleMessages())
     }
 
     /** 同 [asMessageChain] */
@@ -215,6 +215,8 @@ public open class MessageChainBuilder private constructor(
     private var firstConstrainSingleIndex = -1
 
     private fun addAndCheckConstrainSingle(element: SingleMessage): Boolean {
+        return container.add(element)
+        /*
         if (element is ConstrainSingle) {
             if (firstConstrainSingleIndex == -1) {
                 firstConstrainSingleIndex = container.size
@@ -222,7 +224,7 @@ public open class MessageChainBuilder private constructor(
             }
             val key = element.key
 
-            val index = container.indexOfFirst(firstConstrainSingleIndex) { it is ConstrainSingle && it.key == key }
+            val index = container.indexOfFirst(firstConstrainSingleIndex) { it is ConstrainSingle && it.key.isSubKeyOf(key) }
             if (index != -1) {
                 container[index] = element
             } else {
@@ -232,6 +234,6 @@ public open class MessageChainBuilder private constructor(
             return true
         } else {
             return container.add(element)
-        }
+        }*/
     }
 }

+ 24 - 37
mirai-core-api/src/commonMain/kotlin/message/data/MessageSource.kt

@@ -19,12 +19,16 @@ import net.mamoe.mirai.Bot
 import net.mamoe.mirai.IMirai
 import net.mamoe.mirai.Mirai
 import net.mamoe.mirai.contact.*
-import net.mamoe.mirai.message.*
+import net.mamoe.mirai.message.MessageEvent
+import net.mamoe.mirai.message.MessageReceipt
+import net.mamoe.mirai.message.MessageReceipt.Companion.recall
+import net.mamoe.mirai.message.MessageSourceSerializer
 import net.mamoe.mirai.message.data.MessageSource.Key.isAboutFriend
 import net.mamoe.mirai.message.data.MessageSource.Key.isAboutGroup
 import net.mamoe.mirai.message.data.MessageSource.Key.isAboutTemp
 import net.mamoe.mirai.message.data.MessageSource.Key.quote
 import net.mamoe.mirai.utils.LazyProperty
+import net.mamoe.mirai.utils.safeCast
 
 /**
  * 消息源. 消息源存在于 [MessageChain] 中, 用于表示这个消息的来源, 也可以用来分辨 [MessageChain].
@@ -62,8 +66,8 @@ import net.mamoe.mirai.utils.LazyProperty
  * @see buildMessageSource 构造一个 [OfflineMessageSource]
  */
 @Serializable(MessageSourceSerializer::class)
-public sealed class MessageSource : Message, MessageMetadata, ConstrainSingle<MessageSource> {
-    public final override val key: ConstrainSingle.Key<MessageSource> get() = Key
+public sealed class MessageSource : Message, MessageMetadata, ConstrainSingle {
+    public final override val key: MessageKey<MessageSource> get() = Key
 
     /**
      * 所属 [Bot.id]
@@ -132,9 +136,7 @@ public sealed class MessageSource : Message, MessageMetadata, ConstrainSingle<Me
      */
     public final override fun toString(): String = "[mirai:source:$ids,$internalIds]"
 
-    public companion object Key : ConstrainSingle.Key<MessageSource> {
-        override val typeName: String get() = "MessageSource"
-
+    public companion object Key : AbstractMessageKey<MessageSource>({ it.safeCast() }) {
         /**
          * 撤回这条消息. 可撤回自己 2 分钟内发出的消息, 和任意时间的群成员的消息.
          *
@@ -258,9 +260,7 @@ public inline val MessageSource.botOrNull: Bot?
  * @see OnlineMessageSource.toOffline 转为 [OfflineMessageSource]
  */
 public sealed class OnlineMessageSource : MessageSource() {
-    public companion object Key : ConstrainSingle.Key<OnlineMessageSource> {
-        public override val typeName: String get() = "OnlineMessageSource"
-    }
+    public companion object Key : AbstractMessageKey<OnlineMessageSource>({ it.safeCast() })
 
     /**
      * @see botId
@@ -294,9 +294,8 @@ public sealed class OnlineMessageSource : MessageSource() {
      * 由 [机器人主动发送消息][Contact.sendMessage] 产生的 [MessageSource], 可通过 [MessageReceipt] 获得.
      */
     public sealed class Outgoing : OnlineMessageSource() {
-        public companion object Key : ConstrainSingle.Key<Outgoing> {
-            public override val typeName: String get() = "OnlineMessageSource.Outgoing"
-        }
+        public companion object Key :
+            AbstractPolymorphicMessageKey<MessageSource, Outgoing>(MessageSource, { it.safeCast() })
 
         public abstract override val sender: Bot
         public abstract override val target: Contact
@@ -305,9 +304,7 @@ public sealed class OnlineMessageSource : MessageSource() {
         public final override val targetId: Long get() = target.id
 
         public abstract class ToFriend : Outgoing() {
-            public companion object Key : ConstrainSingle.Key<ToFriend> {
-                public override val typeName: String get() = "OnlineMessageSource.Outgoing.ToFriend"
-            }
+            public companion object Key : AbstractPolymorphicMessageKey<Outgoing, ToFriend>(Outgoing, { it.safeCast() })
 
             public abstract override val target: Friend
             public final override val subject: Friend get() = target
@@ -315,9 +312,7 @@ public sealed class OnlineMessageSource : MessageSource() {
         }
 
         public abstract class ToTemp : Outgoing() {
-            public companion object Key : ConstrainSingle.Key<ToTemp> {
-                public override val typeName: String get() = "OnlineMessageSource.Outgoing.ToTemp"
-            }
+            public companion object Key : AbstractPolymorphicMessageKey<Outgoing, ToTemp>(Outgoing, { it.safeCast() })
 
             public abstract override val target: Member
             public val group: Group get() = target.group
@@ -325,9 +320,7 @@ public sealed class OnlineMessageSource : MessageSource() {
         }
 
         public abstract class ToGroup : Outgoing() {
-            public companion object Key : ConstrainSingle.Key<ToGroup> {
-                public override val typeName: String get() = "OnlineMessageSource.Outgoing.ToGroup"
-            }
+            public companion object Key : AbstractPolymorphicMessageKey<Outgoing, ToGroup>(Outgoing, { it.safeCast() })
 
             public abstract override val target: Group
             public final override val subject: Group get() = target
@@ -338,19 +331,14 @@ public sealed class OnlineMessageSource : MessageSource() {
      * 接收到的一条消息的 [MessageSource]
      */
     public sealed class Incoming : OnlineMessageSource() {
-        public companion object Key : ConstrainSingle.Key<Incoming> {
-            public override val typeName: String get() = "OnlineMessageSource.Incoming"
-        }
-
         public abstract override val sender: User
 
         public final override val fromId: Long get() = sender.id
         public final override val targetId: Long get() = target.id
 
         public abstract class FromFriend : Incoming() {
-            public companion object Key : ConstrainSingle.Key<FromFriend> {
-                public override val typeName: String get() = "OnlineMessageSource.Incoming.FromFriend"
-            }
+            public companion object Key :
+                AbstractPolymorphicMessageKey<Incoming, FromFriend>(Incoming, { it.safeCast() })
 
             public abstract override val sender: Friend
             public final override val subject: Friend get() = sender
@@ -359,9 +347,7 @@ public sealed class OnlineMessageSource : MessageSource() {
         }
 
         public abstract class FromTemp : Incoming() {
-            public companion object Key : ConstrainSingle.Key<FromTemp> {
-                public override val typeName: String get() = "OnlineMessageSource.Incoming.FromTemp"
-            }
+            public companion object Key : AbstractPolymorphicMessageKey<Incoming, FromTemp>(Incoming, { it.safeCast() })
 
             public abstract override val sender: Member
             public inline val group: Group get() = sender.group
@@ -370,15 +356,17 @@ public sealed class OnlineMessageSource : MessageSource() {
         }
 
         public abstract class FromGroup : Incoming() {
-            public companion object Key : ConstrainSingle.Key<FromGroup> {
-                public override val typeName: String get() = "OnlineMessageSource.Incoming.FromGroup"
-            }
+            public companion object Key :
+                AbstractPolymorphicMessageKey<Incoming, FromGroup>(Incoming, { it.safeCast() })
 
             public abstract override val sender: Member
             public final override val subject: Group get() = sender.group
             public final override val target: Group get() = group
             public inline val group: Group get() = sender.group
         }
+
+        public companion object Key :
+            AbstractPolymorphicMessageKey<MessageSource, FromTemp>(MessageSource, { it.safeCast() })
     }
 }
 
@@ -389,9 +377,8 @@ public sealed class OnlineMessageSource : MessageSource() {
  * @see buildMessageSource 构建一个 [OfflineMessageSource]
  */
 public abstract class OfflineMessageSource : MessageSource() {
-    public companion object Key : ConstrainSingle.Key<OfflineMessageSource> {
-        public override val typeName: String get() = "OfflineMessageSource"
-    }
+    public companion object Key :
+        AbstractPolymorphicMessageKey<MessageSource, OfflineMessageSource>(MessageSource, { it.safeCast() })
 
     /**
      * 消息种类

+ 4 - 6
mirai-core-api/src/commonMain/kotlin/message/data/QuoteReply.kt

@@ -16,6 +16,7 @@ package net.mamoe.mirai.message.data
 import kotlinx.serialization.Serializable
 import net.mamoe.mirai.Bot
 import net.mamoe.mirai.message.data.MessageSource.Key.recall
+import net.mamoe.mirai.utils.safeCast
 
 
 /**
@@ -39,13 +40,10 @@ import net.mamoe.mirai.message.data.MessageSource.Key.recall
  * @see MessageSource 获取有关消息源的更多信息
  */
 @Serializable
-public data class QuoteReply(public val source: MessageSource) : Message, MessageMetadata, ConstrainSingle<QuoteReply> {
-    public companion object Key : ConstrainSingle.Key<QuoteReply> {
-        public override val typeName: String
-            get() = "QuoteReply"
-    }
+public data class QuoteReply(public val source: MessageSource) : Message, MessageMetadata, ConstrainSingle {
+    public companion object Key : AbstractMessageKey<QuoteReply>({ it.safeCast() })
 
-    public override val key: ConstrainSingle.Key<QuoteReply> get() = Key
+    public override val key: MessageKey<QuoteReply> get() = Key
 
     // TODO: 2020/12/2 QuoteReply.toString
     public override fun toString(): String = "[mirai:quote:${source.ids},${source.internalIds}]"

+ 8 - 17
mirai-core-api/src/commonMain/kotlin/message/data/RichMessage.kt

@@ -15,6 +15,7 @@ package net.mamoe.mirai.message.data
 
 import kotlinx.serialization.Serializable
 import net.mamoe.mirai.utils.MiraiExperimentalApi
+import net.mamoe.mirai.utils.safeCast
 import kotlin.annotation.AnnotationTarget.*
 
 /**
@@ -45,12 +46,14 @@ public interface RichMessage : MessageContent {
      * @suppress 此 API 不稳定, 可能在任意时刻被删除
      */
     @MiraiExperimentalApi
-    public companion object Templates : ConstrainSingle.Key<RichMessage> {
+    public companion object Key :
+        AbstractPolymorphicMessageKey<MessageContent, RichMessage>(MessageContent, { it.safeCast() }) {
 
         /**
          * @suppress 此 API 不稳定, 可能在任意时刻被删除
          */
         @MiraiExperimentalApi
+        @JvmStatic
         public fun share(
             url: String,
             title: String? = null,
@@ -76,9 +79,6 @@ public interface RichMessage : MessageContent {
                     }
                 }
             }
-
-        override val typeName: String
-            get() = "RichMessage"
     }
 }
 
@@ -93,9 +93,7 @@ public interface RichMessage : MessageContent {
  */
 @Serializable
 public data class LightApp(override val content: String) : RichMessage {
-    public companion object Key : ConstrainSingle.Key<LightApp> {
-        public override val typeName: String get() = "LightApp"
-    }
+    public companion object Key : AbstractMessageKey<LightApp>({ it.safeCast() })
 
     public override fun toString(): String = "[mirai:app:$content]"
 }
@@ -116,10 +114,6 @@ public class SimpleServiceMessage(
     public override val serviceId: Int,
     public override val content: String
 ) : ServiceMessage {
-    public companion object Key : ConstrainSingle.Key<ServiceMessage> {
-        public override val typeName: String get() = "ServiceMessage"
-    }
-
     public override fun toString(): String = "[mirai:service:$serviceId,$content]"
 
     public override fun equals(other: Any?): Boolean {
@@ -146,9 +140,8 @@ public class SimpleServiceMessage(
  * @see SimpleServiceMessage
  */
 public interface ServiceMessage : RichMessage {
-    public companion object Key : ConstrainSingle.Key<ServiceMessage> {
-        public override val typeName: String get() = "ServiceMessage"
-    }
+    public companion object Key :
+        AbstractPolymorphicMessageKey<RichMessage, ServiceMessage>(RichMessage, { it.safeCast() })
 
     /**
      * 目前未知, XML 一般为 60, JSON 一般为 1
@@ -258,9 +251,7 @@ internal class LongMessage internal constructor(override val content: String, va
     AbstractServiceMessage() {
     override val serviceId: Int get() = 35
 
-    companion object Key : ConstrainSingle.Key<LongMessage> {
-        override val typeName: String get() = "LongMessage"
-    }
+    companion object Key : AbstractPolymorphicMessageKey<ServiceMessage, LongMessage>(ServiceMessage, { it.safeCast() })
 }
 
 @Serializable

+ 4 - 8
mirai-core-api/src/commonMain/kotlin/message/data/Voice.kt

@@ -12,6 +12,7 @@ package net.mamoe.mirai.message.data
 import kotlinx.serialization.Serializable
 import net.mamoe.mirai.utils.MiraiExperimentalApi
 import net.mamoe.mirai.utils.MiraiInternalApi
+import net.mamoe.mirai.utils.safeCast
 
 
 /**
@@ -21,10 +22,8 @@ import net.mamoe.mirai.utils.MiraiInternalApi
 @MiraiExperimentalApi
 public abstract class PttMessage : MessageContent {
 
-    public companion object Key : ConstrainSingle.Key<PttMessage> {
-        public override val typeName: String
-            get() = "PttMessage"
-    }
+    public companion object Key :
+        AbstractPolymorphicMessageKey<MessageContent, PttMessage>(MessageContent, { it.safeCast() })
 
     public abstract val fileName: String
     public abstract val md5: ByteArray
@@ -43,10 +42,7 @@ public class Voice @MiraiInternalApi constructor(
     private val _url: String
 ) : PttMessage() {
 
-    public companion object Key : ConstrainSingle.Key<Voice> {
-        override val typeName: String
-            get() = "Voice"
-    }
+    public companion object Key : AbstractPolymorphicMessageKey<PttMessage, Voice>(PttMessage, { it.safeCast() })
 
     public val url: String?
         get() = when {

+ 39 - 108
mirai-core-api/src/commonMain/kotlin/message/data/impl.kt

@@ -22,13 +22,13 @@ import kotlin.native.concurrent.SharedImmutable
 //// IMPLEMENTATIONS ////
 /////////////////////////
 
-private fun Message.hasDuplicationOfConstrain(key: ConstrainSingle.Key<*>): Boolean {
+private fun Message.hasDuplicationOfConstrain(key: MessageKey<*>): Boolean {
     return when (this) {
-        is SingleMessage -> (this as? ConstrainSingle<*>)?.key == key
+        is SingleMessage -> (this as? ConstrainSingle)?.key == key
         is CombinedMessage -> return this.left.hasDuplicationOfConstrain(key) || this.tail.hasDuplicationOfConstrain(key)
-        is SingleMessageChainImpl -> (this.delegate as? ConstrainSingle<*>)?.key == key
-        is MessageChainImplByCollection -> this.delegate.any { (it as? ConstrainSingle<*>)?.key == key }
-        is MessageChainImplBySequence -> this.any { (it as? ConstrainSingle<*>)?.key == key }
+        is SingleMessageChainImpl -> (this.delegate as? ConstrainSingle)?.key == key
+        is MessageChainImplByCollection -> this.delegate.any { (it as? ConstrainSingle)?.key == key }
+        is MessageChainImplBySequence -> this.any { (it as? ConstrainSingle)?.key == key }
         else -> error("stub")
     }
 }
@@ -63,7 +63,7 @@ internal fun Message.contentEqualsImpl(another: Message, ignoreCase: Boolean): B
 internal fun Message.followedByImpl(tail: Message): MessageChain {
     when {
         this is SingleMessage && tail is SingleMessage -> {
-            if (this is ConstrainSingle<*> && tail is ConstrainSingle<*>) {
+            if (this is ConstrainSingle && tail is ConstrainSingle) {
                 if (this.key == tail.key)
                     return SingleMessageChainImpl(tail)
             }
@@ -73,9 +73,9 @@ internal fun Message.followedByImpl(tail: Message): MessageChain {
         this is SingleMessage -> { // tail is not
             tail as MessageChain
 
-            if (this is ConstrainSingle<*>) {
+            if (this is ConstrainSingle) {
                 val key = this.key
-                if (tail.any { (it as? ConstrainSingle<*>)?.key == key }) {
+                if (tail.any { (it as? ConstrainSingle)?.key == key }) {
                     return tail
                 }
             }
@@ -85,19 +85,8 @@ internal fun Message.followedByImpl(tail: Message): MessageChain {
         tail is SingleMessage -> {
             this as MessageChain
 
-            if (tail is ConstrainSingle<*> && this.hasDuplicationOfConstrain(tail.key)) {
-                val iterator = this.iterator()
-                var tailUsed = false
-                return MessageChainImplByCollection(
-                    constrainSingleMessagesImpl {
-                        if (iterator.hasNext()) {
-                            iterator.next()
-                        } else if (!tailUsed) {
-                            tailUsed = true
-                            tail
-                        } else null
-                    }
-                )
+            if (tail is ConstrainSingle && this.hasDuplicationOfConstrain(tail.key)) {
+                return MessageChainImplByCollection(constrainSingleMessagesImpl(this.asSequence() + tail))
             }
 
             return CombinedMessage(this, tail)
@@ -107,20 +96,8 @@ internal fun Message.followedByImpl(tail: Message): MessageChain {
             this as MessageChain
             tail as MessageChain
 
-            var iterator = this.iterator()
-            var tailUsed = false
             return MessageChainImplByCollection(
-                constrainSingleMessagesImpl {
-                    if (iterator.hasNext()) {
-                        iterator.next()
-                    } else if (!tailUsed) {
-                        tailUsed = true
-                        iterator = tail.iterator()
-                        if (iterator.hasNext()) {
-                            iterator.next()
-                        } else null
-                    } else null
-                }
+                constrainSingleMessagesImpl(this.asSequence() + tail)
             )
         }
     }
@@ -128,53 +105,40 @@ internal fun Message.followedByImpl(tail: Message): MessageChain {
 
 
 @JvmSynthetic
-internal fun Sequence<SingleMessage>.constrainSingleMessages(): List<SingleMessage> {
-    val iterator = this.iterator()
-    return constrainSingleMessagesImpl supplier@{
-        if (iterator.hasNext()) {
-            iterator.next()
-        } else null
-    }
-}
+internal fun Sequence<SingleMessage>.constrainSingleMessages(): List<SingleMessage> =
+    constrainSingleMessagesImpl(this.asSequence())
 
+/**
+ * - [Sequence.toMutableList]
+ * - Replace in-place with marker null
+ * - [Iterable.filterNotNull]
+ */
 @MiraiExperimentalApi
 @JvmSynthetic
-internal inline fun constrainSingleMessagesImpl(iterator: () -> SingleMessage?): ArrayList<SingleMessage> {
-    val list = ArrayList<SingleMessage>()
-    var firstConstrainIndex = -1
-
-    var next: SingleMessage?
-    do {
-        next = iterator()
-        next?.let { singleMessage ->
-            if (singleMessage is ConstrainSingle<*>) {
-                if (firstConstrainIndex == -1) {
-                    firstConstrainIndex = list.size // we are going to add one
-                } else {
-                    val key = singleMessage.key
-                    val index = list.indexOfFirst(firstConstrainIndex) { it is ConstrainSingle<*> && it.key == key }
-                    if (index != -1) {
-                        list[index] = singleMessage
-                        return@let
-                    }
+internal fun constrainSingleMessagesImpl(sequence: Sequence<SingleMessage>): List<SingleMessage> {
+    val list: MutableList<SingleMessage?> = sequence.toMutableList()
+
+    for (singleMessage in list.asReversed()) {
+        if (singleMessage is ConstrainSingle) {
+            val key = singleMessage.key.topmostKey
+            val firstOccurrence = list.first { it != null && key.isInstance(it) } // may be singleMessage itself
+            list.replaceAll {
+                when {
+                    it == null -> null
+                    it === firstOccurrence -> singleMessage
+                    key.isInstance(it) -> null // remove duplicates
+                    else -> it
                 }
             }
+        }
+    }
 
-            list.add(singleMessage)
-        } ?: return list
-    } while (true)
+    return list.filterNotNull()
 }
 
 @JvmSynthetic
-
-internal fun Iterable<SingleMessage>.constrainSingleMessages(): List<SingleMessage> {
-    val iterator = this.iterator()
-    return constrainSingleMessagesImpl supplier@{
-        if (iterator.hasNext()) {
-            iterator.next()
-        } else null
-    }
-}
+internal fun Iterable<SingleMessage>.constrainSingleMessages(): List<SingleMessage> =
+    constrainSingleMessagesImpl(this.asSequence())
 
 @JvmSynthetic
 internal inline fun <T> List<T>.indexOfFirst(offset: Int, predicate: (T) -> Boolean): Int {
@@ -188,42 +152,9 @@ internal inline fun <T> List<T>.indexOfFirst(offset: Int, predicate: (T) -> Bool
 
 @JvmSynthetic
 @Suppress("UNCHECKED_CAST", "DEPRECATION_ERROR", "DEPRECATION")
-internal fun <M : Message> MessageChain.firstOrNullImpl(key: ConstrainSingle.Key<M>): M? = when (key) {
-    //  OnlineImage -> firstIsInstanceOrNull<OnlineImage>()
-    //  OfflineImage -> firstIsInstanceOrNull<OfflineImage>()
-    QuoteReply -> firstIsInstanceOrNull<QuoteReply>()
-    MessageSource -> firstIsInstanceOrNull<MessageSource>()
-    OnlineMessageSource -> firstIsInstanceOrNull<OnlineMessageSource>()
-    OfflineMessageSource -> firstIsInstanceOrNull<OfflineMessageSource>()
-    OnlineMessageSource.Outgoing -> firstIsInstanceOrNull<OnlineMessageSource.Outgoing>()
-    OnlineMessageSource.Outgoing.ToGroup -> firstIsInstanceOrNull<OnlineMessageSource.Outgoing.ToGroup>()
-    OnlineMessageSource.Outgoing.ToFriend -> firstIsInstanceOrNull<OnlineMessageSource.Outgoing.ToFriend>()
-    OnlineMessageSource.Incoming -> firstIsInstanceOrNull<OnlineMessageSource.Incoming>()
-    OnlineMessageSource.Incoming.FromGroup -> firstIsInstanceOrNull<OnlineMessageSource.Incoming.FromGroup>()
-    OnlineMessageSource.Incoming.FromFriend -> firstIsInstanceOrNull<OnlineMessageSource.Incoming.FromFriend>()
-    OnlineMessageSource -> firstIsInstanceOrNull<OnlineMessageSource>()
-    LongMessage -> firstIsInstanceOrNull()
-    RichMessage -> firstIsInstanceOrNull<RichMessage>()
-    LightApp -> firstIsInstanceOrNull<LightApp>()
-    FlashImage -> firstIsInstanceOrNull<FlashImage>()
-    GroupFlashImage -> firstIsInstanceOrNull<GroupFlashImage>()
-    FriendFlashImage -> firstIsInstanceOrNull<FriendFlashImage>()
-    PttMessage -> firstIsInstanceOrNull<PttMessage>()
-    Voice -> firstIsInstanceOrNull<Voice>()
-    else -> {
-        this.forEach { message ->
-            if (message is CustomMessage) {
-                @Suppress("UNCHECKED_CAST")
-                if (message.getFactory() == key) {
-                    return message as? M
-                        ?: error("cannot cast ${message::class.qualifiedName}. Make sure CustomMessage.getFactory returns a factory that has a generic type which is the same as the type of your CustomMessage")
-                }
-            }
-        }
-
-        null
-    }
-} as M?
+internal fun <M : SingleMessage> MessageChain.getImpl(key: MessageKey<M>): M? {
+    return this.asSequence().mapNotNull { key.safeCast.invoke(it) }.firstOrNull()
+}
 
 /**
  * 使用 [Collection] 作为委托的 [MessageChain]

+ 53 - 0
mirai-core-api/src/commonMain/kotlin/net/mamoe/mirai/message/data/MessageKey.kt

@@ -0,0 +1,53 @@
+/*
+ * Copyright 2019-2020 Mamoe Technologies and contributors.
+ *
+ *  此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ *  Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ *  https://github.com/mamoe/mirai/blob/master/LICENSE
+ */
+
+package net.mamoe.mirai.message.data
+
+/**
+ * 类型 Key. 由伴生对象实现, 表示一个 [Message] 对象的类型.
+ *
+ * 除 [MessageChain] 外, 每个 [Message] 类型都拥有一个伴生对象 (companion object) 来持有一个 Key
+ *
+ * 在 [MessageChain.get] 时将会使用到这个 Key 进行判断类型.
+ *
+ * #### 用例
+ * [MessageChain.get][MessageChain.get]: 允许使用数组访问操作符获取指定类型的消息元素
+ * ```
+ * val image: Image = chain[Image]
+ * ```
+ *
+ * @param M 指代持有这个 Key 的消息类型
+ */
+@ExperimentalMessageKey
+public interface MessageKey<out M : SingleMessage> {
+    public val safeCast: (SingleMessage) -> M?
+}
+
+@ExperimentalMessageKey
+public fun MessageKey<*>.isInstance(message: SingleMessage): Boolean = this.safeCast(message) != null
+
+@ExperimentalMessageKey
+public tailrec fun <A : SingleMessage, B : SingleMessage> MessageKey<A>.isSubKeyOf(baseKey: MessageKey<B>): Boolean {
+    return when {
+        this === baseKey -> true
+        this is AbstractPolymorphicMessageKey<*, *> -> {
+            this.baseKey.isSubKeyOf(baseKey)
+        }
+        else -> false
+    }
+}
+
+@ExperimentalMessageKey
+public val <A : SingleMessage> MessageKey<A>.topmostKey: MessageKey<*>
+    get() = when (this) {
+        is AbstractPolymorphicMessageKey<*, *> -> {
+            this.baseKey.topmostKey
+        }
+        else -> this
+    }

+ 5 - 6
mirai-core-api/src/commonTest/kotlin/message.data/ConstrainSingleTest.kt

@@ -8,22 +8,21 @@
  */
 package  net.mamoe.mirai.message.data
 
+import net.mamoe.mirai.utils.safeCast
 import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertSame
 import kotlin.test.assertTrue
 
 
-internal class TestConstrainSingleMessage : ConstrainSingle<TestConstrainSingleMessage>, Any() {
-    companion object Key : ConstrainSingle.Key<TestConstrainSingleMessage> {
-        override val typeName: String
-            get() = "TestMessage"
-    }
+@OptIn(ExperimentalMessageKey::class)
+internal class TestConstrainSingleMessage : ConstrainSingle, Any() {
+    companion object Key : AbstractMessageKey<TestConstrainSingleMessage>({ it.safeCast() })
 
     override fun toString(): String = "<TestConstrainSingleMessage#${super.hashCode()}>"
     override fun contentToString(): String = ""
 
-    override val key: ConstrainSingle.Key<TestConstrainSingleMessage>
+    override val key: MessageKey<TestConstrainSingleMessage>
         get() = Key
 }
 

+ 107 - 0
mirai-core-api/src/commonTest/kotlin/message.data/MessageKeyTest.kt

@@ -0,0 +1,107 @@
+/*
+ * Copyright 2019-2020 Mamoe Technologies and contributors.
+ *
+ *  此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ *  Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ *  https://github.com/mamoe/mirai/blob/master/LICENSE
+ */
+
+package net.mamoe.mirai.message.data
+
+import net.mamoe.mirai.utils.safeCast
+import org.junit.jupiter.api.Test
+import kotlin.test.assertEquals
+
+
+@OptIn(ExperimentalMessageKey::class)
+private open class TestStandaloneConstrainSingleMessage : ConstrainSingle, MessageContent {
+    companion object Key : AbstractMessageKey<TestStandaloneConstrainSingleMessage>({ it.safeCast() })
+
+    override fun toString(): String = "<TestStandaloneConstrainSingleMessage#${super.hashCode()}>"
+    override fun contentToString(): String = ""
+
+    override val key: MessageKey<TestStandaloneConstrainSingleMessage> get() = Key
+}
+
+@OptIn(ExperimentalMessageKey::class)
+private class TestPolymorphicConstrainSingleMessage : ConstrainSingle, TestStandaloneConstrainSingleMessage(),
+    MessageContent {
+    companion object Key :
+        AbstractPolymorphicMessageKey<TestStandaloneConstrainSingleMessage, TestPolymorphicConstrainSingleMessage>(
+            TestStandaloneConstrainSingleMessage, { it.safeCast() }
+        )
+
+    override fun toString(): String = "<TestPolymorphicConstrainSingleMessage#${super.hashCode()}>"
+    override fun contentToString(): String = ""
+
+    override val key: MessageKey<TestPolymorphicConstrainSingleMessage> get() = Key
+}
+
+@OptIn(ExperimentalMessageKey::class)
+private class TestPolymorphicConstrainSingleMessageOverridingMessageContent : ConstrainSingle, MessageContent,
+    TestStandaloneConstrainSingleMessage() {
+    companion object Key :
+        AbstractPolymorphicMessageKey<MessageContent, TestPolymorphicConstrainSingleMessageOverridingMessageContent>(
+            MessageContent, { it.safeCast() }
+        )
+
+    override fun toString(): String =
+        "<TestPolymorphicConstrainSingleMessageOverridingMessageContent#${super.hashCode()}>"
+
+    override fun contentToString(): String = ""
+
+    override val key: MessageKey<TestPolymorphicConstrainSingleMessageOverridingMessageContent> get() = Key
+}
+
+
+internal class MessageKeyTest {
+    @OptIn(ExperimentalMessageKey::class)
+    @Test
+    fun `test polymorphism get`() {
+        val constrainSingle: TestStandaloneConstrainSingleMessage
+        val chain = buildMessageChain {
+            +TestStandaloneConstrainSingleMessage()
+            +PlainText("test")
+            +TestStandaloneConstrainSingleMessage()
+            +PlainText("test")
+            +TestStandaloneConstrainSingleMessage().also { constrainSingle = it }
+        }
+
+        assertEquals(constrainSingle, chain[MessageContent])
+        assertEquals(constrainSingle, chain[TestStandaloneConstrainSingleMessage])
+    }
+
+    @OptIn(ExperimentalMessageKey::class)
+    @Test
+    fun `test polymorphism override base`() {
+        val constrainSingle: TestPolymorphicConstrainSingleMessage
+        val chain = buildMessageChain {
+            +TestStandaloneConstrainSingleMessage()
+            +PlainText("test")
+            +TestStandaloneConstrainSingleMessage()
+            +PlainText("test")
+            +TestPolymorphicConstrainSingleMessage().also { constrainSingle = it }
+        }
+
+        assertEquals(constrainSingle, chain[MessageContent])
+        assertEquals(constrainSingle, chain[TestPolymorphicConstrainSingleMessage])
+    }
+
+    @OptIn(ExperimentalMessageKey::class)
+    @Test
+    fun `test polymorphism override message content`() {
+        val constrainSingle: TestPolymorphicConstrainSingleMessageOverridingMessageContent
+        val chain = buildMessageChain {
+            +TestStandaloneConstrainSingleMessage()
+            +PlainText("test")
+            +TestStandaloneConstrainSingleMessage()
+            +PlainText("test")
+            +TestPolymorphicConstrainSingleMessageOverridingMessageContent().also { constrainSingle = it }
+        }
+
+        assertEquals(constrainSingle, chain[MessageContent])
+        assertEquals<Any?>(constrainSingle, chain[TestStandaloneConstrainSingleMessage])
+        assertEquals(1, chain.size)
+    }
+}

+ 2 - 2
mirai-core/src/commonMain/kotlin/QQAndroidBot.common.kt

@@ -124,7 +124,7 @@ internal abstract class QQAndroidBotBase constructor(
 
 internal val EMPTY_BYTE_ARRAY = ByteArray(0)
 
-internal fun RichMessage.Templates.longMessage(brief: String, resId: String, timeSeconds: Long): RichMessage {
+internal fun RichMessage.Key.longMessage(brief: String, resId: String, timeSeconds: Long): RichMessage {
     val limited: String = if (brief.length > 30) {
         brief.take(30) + "…"
     } else {
@@ -151,7 +151,7 @@ internal fun RichMessage.Templates.longMessage(brief: String, resId: String, tim
 }
 
 
-internal fun RichMessage.Templates.forwardMessage(
+internal fun RichMessage.Key.forwardMessage(
     resId: String,
     timeSeconds: Long,
     forwardMessage: ForwardMessage,

+ 5 - 22
mirai-core/src/commonMain/kotlin/message/imagesImpl.kt

@@ -13,9 +13,9 @@ package net.mamoe.mirai.internal.message
 
 import kotlinx.serialization.Serializable
 import net.mamoe.mirai.Bot
+import net.mamoe.mirai.IMirai
 import net.mamoe.mirai.contact.Contact
 import net.mamoe.mirai.contact.Group
-import net.mamoe.mirai.internal.MiraiImpl
 import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
 import net.mamoe.mirai.internal.utils.hexToBytes
 import net.mamoe.mirai.message.data.*
@@ -142,38 +142,21 @@ internal class ExperimentalDeferredImage internal constructor(
     override val imageId: String = externalImage.calculateImageResourceId()
 }
 
-internal val firstOnlineBotInstance: Bot get() = Bot.botInstancesSequence.firstOrNull() ?: error("No Bot available")
-
 @Suppress("EXPOSED_SUPER_INTERFACE")
 internal interface OnlineImage : Image, ConstOriginUrlAware {
-    companion object Key : ConstrainSingle.Key<OnlineImage> {
-        override val typeName: String get() = "OnlineImage"
-    }
-
     override val originUrl: String
 }
 
 /**
  * 离线的图片, 即为客户端主动上传到服务器而获得的 [Image] 实例.
- * 不能直接获取它在服务器上的链接. 需要通过 [Bot.queryImageUrl] 查询
+ * 不能直接获取它在服务器上的链接. 需要通过 [IMirai.queryImageUrl] 查询
  *
  * 一般由 [Contact.uploadImage] 得到
  */
-internal interface OfflineImage : Image {
-    companion object Key : ConstrainSingle.Key<OfflineImage> {
-        override val typeName: String get() = "OfflineImage"
-    }
-}
-
-@JvmSynthetic
-internal suspend fun OfflineImage.queryUrl(): String {
-    @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
-    val bot = Bot._instances.peekFirst()?.get() ?: error("No Bot available to query image url")
-    return MiraiImpl.queryImageUrl(bot, this)
-}
+internal interface OfflineImage : Image
 
 /**
- * 通过 [Group.uploadImage] 上传得到的 [GroupImage]. 它的链接需要查询 [Bot.queryImageUrl]
+ * 通过 [Group.uploadImage] 上传得到的 [GroupImage]. 它的链接需要查询 [IMirai.queryImageUrl]
  *
  * @param imageId 参考 [Image.imageId]
  */
@@ -204,7 +187,7 @@ internal data class OfflineGroupImage(
 internal abstract class OnlineGroupImage : GroupImage(), OnlineImage
 
 /**
- * 通过 [Group.uploadImage] 上传得到的 [GroupImage]. 它的链接需要查询 [Bot.queryImageUrl]
+ * 通过 [Group.uploadImage] 上传得到的 [GroupImage]. 它的链接需要查询 [IMirai.queryImageUrl]
  *
  * @param imageId 参考 [Image.imageId]
  */

+ 6 - 10
mirai-core/src/jvmTest/kotlin/ContentEqualsTest.kt

@@ -9,25 +9,21 @@
 
 package net.mamoe.mirai.internal
 
-import net.mamoe.mirai.message.data.ConstrainSingle
-import net.mamoe.mirai.message.data.Image
-import net.mamoe.mirai.message.data.buildMessageChain
-import net.mamoe.mirai.message.data.content
+import net.mamoe.mirai.message.data.*
+import net.mamoe.mirai.utils.safeCast
 import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertFalse
 import kotlin.test.assertTrue
 
-internal class TestConstrainSingleMessage : ConstrainSingle<TestConstrainSingleMessage>, Any() {
-    companion object Key : ConstrainSingle.Key<TestConstrainSingleMessage> {
-        override val typeName: String
-            get() = "TestMessage"
-    }
+internal class TestConstrainSingleMessage : ConstrainSingle, Any() {
+    companion object Key : AbstractMessageKey<TestConstrainSingleMessage>({ it.safeCast() })
 
     override fun toString(): String = "<TestConstrainSingleMessage#${super.hashCode()}>"
     override fun contentToString(): String = ""
 
-    override val key: ConstrainSingle.Key<TestConstrainSingleMessage>
+    @ExperimentalMessageKey
+    override val key: MessageKey<TestConstrainSingleMessage>
         get() = Key
 }