소스 검색

Introduce `ContactMessage` to replace `MessagePacket<*, *>`

Him188 6 년 전
부모
커밋
32553fad2b
22개의 변경된 파일268개의 추가작업 그리고 91개의 파일을 삭제
  1. 7 0
      CHANGELOG.md
  2. 1 1
      mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/QQImpl.kt
  3. 1 1
      mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/Contact.kt
  4. 4 4
      mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/ContactJavaFriendlyAPI.kt
  5. 1 1
      mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/QQ.kt
  6. 33 21
      mirai-core/src/androidMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt
  7. 1 1
      mirai-core/src/androidMain/kotlin/net/mamoe/mirai/message/MessageReceipt.kt
  8. 1 1
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt
  9. 1 1
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt
  10. 8 8
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/select.kt
  11. 5 5
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscribeMessages.kt
  12. 1 1
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/FriendMessage.kt
  13. 1 1
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt
  14. 81 30
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt
  15. 9 6
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt
  16. 1 1
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LazyProperty.kt
  17. 99 0
      mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/SoftRef.kt
  18. 1 1
      mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/Contact.kt
  19. 4 4
      mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/ContactJavaFriendlyAPI.kt
  20. 1 1
      mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/QQ.kt
  21. 6 1
      mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt
  22. 1 1
      mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessageReceipt.kt

+ 7 - 0
CHANGELOG.md

@@ -2,6 +2,13 @@
 
 开发版本. 频繁更新, 不保证高稳定性
 
+## `0.32.0`
+- 将部分 internal API 从 `mirai-core` 移至 `mirai-core-qqandroid`
+- 将部分意外 public 的 API 改为 internal.
+- 使用 Kotlin 1.3.71, 兼容原使用 Kotlin 1.4-M1 编译的代码.
+- 优化 `BotConfiguration`, 去掉 DSL 操作, 使用  `fileBasedDeviceInfo(filename)` 等函数替代. (兼容原操作方式, 计划于 `0.34.0` 删除)
+- 调整长消息判定权重, 具体为: Chinese char=4, English char=1, Quote=700, Image=800, 其他消息类型转换为字符串后判断长度.
+
 ## `0.31.4`  2020/3/31
 - 修复 At 在手机上显示错误的问题
 

+ 1 - 1
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/QQImpl.kt

@@ -75,7 +75,7 @@ internal class QQImpl(
 
     @JvmSynthetic
     @Suppress("DuplicatedCode")
-    override suspend fun sendMessage(message: Message): MessageReceipt<out QQ> {
+    override suspend fun sendMessage(message: Message): MessageReceipt<QQ> {
         val event = MessageSendEvent.FriendMessageSendEvent(this, message.asMessageChain()).broadcast()
         if (event.isCancelled) {
             throw EventCancelledException("cancelled by FriendMessageSendEvent")

+ 1 - 1
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/Contact.kt

@@ -66,7 +66,7 @@ actual abstract class Contact : CoroutineScope, ContactJavaFriendlyAPI() {
      * @return 消息回执. 可 [引用回复][MessageReceipt.quote](仅群聊)或 [撤回][MessageReceipt.recall] 这条消息.
      */
     @JvmSynthetic
-    actual abstract suspend fun sendMessage(message: Message): MessageReceipt<out Contact>
+    actual abstract suspend fun sendMessage(message: Message): MessageReceipt<Contact>
 
     /**
      * 上传一个图片以备发送.

+ 4 - 4
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/ContactJavaFriendlyAPI.kt

@@ -62,14 +62,14 @@ actual abstract class ContactJavaFriendlyAPI {
      */
     @Throws(EventCancelledException::class, IllegalStateException::class)
     @JvmName("sendMessage")
-    open fun __sendMessageBlockingForJava__(message: Message): MessageReceipt<out Contact> {
+    open fun __sendMessageBlockingForJava__(message: Message): MessageReceipt<Contact> {
         return runBlocking {
             sendMessage(message)
         }
     }
 
     @JvmName("sendMessage")
-    open fun __sendMessageBlockingForJava__(message: String): MessageReceipt<out Contact> {
+    open fun __sendMessageBlockingForJava__(message: String): MessageReceipt<Contact> {
         return runBlocking { sendMessage(message) }
     }
 
@@ -143,7 +143,7 @@ actual abstract class ContactJavaFriendlyAPI {
      * @see Contact.sendMessage
      */
     @JvmName("sendMessageAsync")
-    open fun __sendMessageAsyncForJava__(message: Message): Future<MessageReceipt<out Contact>> {
+    open fun __sendMessageAsyncForJava__(message: Message): Future<MessageReceipt<Contact>> {
         return future { sendMessage(message) }
     }
 
@@ -152,7 +152,7 @@ actual abstract class ContactJavaFriendlyAPI {
      * @see Contact.sendMessage
      */
     @JvmName("sendMessageAsync")
-    open fun __sendMessageAsyncForJava__(message: String): Future<MessageReceipt<out Contact>> {
+    open fun __sendMessageAsyncForJava__(message: String): Future<MessageReceipt<Contact>> {
         return future { sendMessage(message) }
     }
 

+ 1 - 1
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/QQ.kt

@@ -90,7 +90,7 @@ actual abstract class QQ : Contact(), CoroutineScope {
      * @return 消息回执. 可进行撤回 ([MessageReceipt.recall])
      */
     @JvmSynthetic
-    actual abstract override suspend fun sendMessage(message: Message): MessageReceipt<out QQ>
+    actual abstract override suspend fun sendMessage(message: Message): MessageReceipt<QQ>
 
     /**
      * 上传一个图片以备发送.

+ 33 - 21
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt

@@ -9,36 +9,48 @@
 
 package net.mamoe.mirai.message
 
+import android.graphics.Bitmap
+import kotlinx.io.core.Input
 import net.mamoe.mirai.contact.Contact
 import net.mamoe.mirai.contact.QQ
+import net.mamoe.mirai.message.data.Image
 import net.mamoe.mirai.utils.MiraiInternalAPI
+import java.io.File
+import java.io.InputStream
+import java.net.URL
 
 /**
  * 平台相关扩展
  */
+@Suppress("DEPRECATION")
+@Deprecated(
+    message = "use ContactMessage",
+    replaceWith = ReplaceWith("ContactMessage", "net.mamoe.mirai.message.ContactMessage")
+)
 @OptIn(MiraiInternalAPI::class)
-actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual constructor() : MessagePacketBase<TSender, TSubject>() {
-    //   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)
-    //suspend inline fun uploadImage(image: InputStream): Image = subject.uploadImage(image)
-    //suspend inline fun uploadImage(image: File): Image = subject.uploadImage(image)
+actual sealed class MessagePacket<TSender : QQ, TSubject : Contact> actual constructor() :
+    MessagePacketBase<TSender, TSubject>() {
+    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)
+    suspend inline fun uploadImage(image: InputStream): Image = subject.uploadImage(image)
+    suspend inline fun uploadImage(image: File): Image = subject.uploadImage(image)
 
-    //// suspend inline fun sendImage(image: Bitmap) = subject.sendImage(image)
-    //suspend inline fun sendImage(image: URL) = subject.sendImage(image)
-    //suspend inline fun sendImage(image: Input) = subject.sendImage(image)
-    //suspend inline fun sendImage(image: InputStream) = subject.sendImage(image)
-    //suspend inline fun sendImage(image: File) = subject.sendImage(image)
+    suspend inline fun sendImage(image: Bitmap): MessageReceipt<TSubject> = subject.sendImage(image)
+    suspend inline fun sendImage(image: URL): MessageReceipt<TSubject> = subject.sendImage(image)
+    suspend inline fun sendImage(image: Input): MessageReceipt<TSubject> = subject.sendImage(image)
+    suspend inline fun sendImage(image: InputStream): MessageReceipt<TSubject> = subject.sendImage(image)
+    suspend inline fun sendImage(image: File): MessageReceipt<TSubject> = subject.sendImage(image)
 
-    ////  suspend inline fun Bitmap.upload(): Image = upload(subject)
-    //suspend inline fun URL.uploadAsImage(): Image = uploadAsImage(subject)
-    //suspend inline fun Input.uploadAsImage(): Image = uploadAsImage(subject)
-    //suspend inline fun InputStream.uploadAsImage(): Image = uploadAsImage(subject)
-    //suspend inline fun File.uploadAsImage(): Image = uploadAsImage(subject)
+    suspend inline fun Bitmap.upload(): Image = upload(subject)
+    suspend inline fun URL.uploadAsImage(): Image = uploadAsImage(subject)
+    suspend inline fun Input.uploadAsImage(): Image = uploadAsImage(subject)
+    suspend inline fun InputStream.uploadAsImage(): Image = uploadAsImage(subject)
+    suspend inline fun File.uploadAsImage(): Image = uploadAsImage(subject)
 
-    ////  suspend inline fun Bitmap.send() = sendTo(subject)
-    //suspend inline fun URL.sendAsImage() = sendAsImageTo(subject)
-    //suspend inline fun Input.sendAsImage() = sendAsImageTo(subject)
-    //suspend inline fun InputStream.sendAsImage() = sendAsImageTo(subject)
-    //suspend inline fun File.sendAsImage() = sendAsImageTo(subject)
+    suspend inline fun Bitmap.send(): MessageReceipt<TSubject> = sendTo(subject)
+    suspend inline fun URL.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject)
+    suspend inline fun Input.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject)
+    suspend inline fun InputStream.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject)
+    suspend inline fun File.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject)
 }

+ 1 - 1
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/message/MessageReceipt.kt

@@ -29,7 +29,7 @@ import net.mamoe.mirai.utils.unsafeWeakRef
  */
 @Suppress("FunctionName")
 @OptIn(MiraiInternalAPI::class)
-actual open class MessageReceipt<C : Contact> @OptIn(ExperimentalMessageSource::class)
+actual open class MessageReceipt<out C : Contact> @OptIn(ExperimentalMessageSource::class)
 actual constructor(
     actual val source: MessageSource,
     target: C,

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

@@ -69,7 +69,7 @@ expect abstract class Contact() : CoroutineScope, ContactJavaFriendlyAPI {
      * @return 消息回执. 可 [引用回复][MessageReceipt.quote](仅群聊)或 [撤回][MessageReceipt.recall] 这条消息.
      */
     @JvmSynthetic
-    abstract suspend fun sendMessage(message: Message): MessageReceipt<out Contact>
+    abstract suspend fun sendMessage(message: Message): MessageReceipt<Contact>
 
     /**
      * 上传一个图片以备发送.

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

@@ -99,7 +99,7 @@ expect abstract class QQ() : Contact, CoroutineScope {
      * @return 消息回执. 可进行撤回 ([MessageReceipt.recall])
      */
     @JvmSynthetic
-    abstract override suspend fun sendMessage(message: Message): MessageReceipt<out QQ>
+    abstract override suspend fun sendMessage(message: Message): MessageReceipt<QQ>
 
     /**
      * 上传一个图片以备发送.

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

@@ -10,7 +10,7 @@
 package net.mamoe.mirai.event
 
 import kotlinx.coroutines.*
-import net.mamoe.mirai.message.MessagePacket
+import net.mamoe.mirai.message.ContactMessage
 import net.mamoe.mirai.message.data.Message
 import net.mamoe.mirai.message.data.PlainText
 import net.mamoe.mirai.message.isContextIdenticalWith
@@ -59,7 +59,7 @@ import kotlin.jvm.JvmSynthetic
  */
 @SinceMirai("0.29.0")
 @Suppress("unused")
-suspend inline fun <reified T : MessagePacket<*, *>> T.whileSelectMessages(
+suspend inline fun <reified T : ContactMessage> T.whileSelectMessages(
     timeoutMillis: Long = -1,
     crossinline selectBuilder: @MessageDsl MessageSelectBuilder<T, Boolean>.() -> Unit
 ) = whileSelectMessagesImpl(timeoutMillis, selectBuilder)
@@ -71,7 +71,7 @@ suspend inline fun <reified T : MessagePacket<*, *>> T.whileSelectMessages(
 @MiraiExperimentalAPI
 @SinceMirai("0.29.0")
 @JvmName("selectMessages1")
-suspend inline fun <reified T : MessagePacket<*, *>> T.selectMessagesUnit(
+suspend inline fun <reified T : ContactMessage> T.selectMessagesUnit(
     timeoutMillis: Long = -1,
     crossinline selectBuilder: @MessageDsl MessageSelectBuilderUnit<T, Unit>.() -> Unit
 ) = selectMessagesImpl(timeoutMillis, true, selectBuilder)
@@ -100,7 +100,7 @@ suspend inline fun <reified T : MessagePacket<*, *>> T.selectMessagesUnit(
 @SinceMirai("0.29.0")
 @Suppress("unused") // false positive
 // @BuilderInference // https://youtrack.jetbrains.com/issue/KT-37716
-suspend inline fun <reified T : MessagePacket<*, *>, R> T.selectMessages(
+suspend inline fun <reified T : ContactMessage, R> T.selectMessages(
     timeoutMillis: Long = -1,
     // @BuilderInference
     crossinline selectBuilder: @MessageDsl MessageSelectBuilder<T, R>.() -> Unit
@@ -114,7 +114,7 @@ suspend inline fun <reified T : MessagePacket<*, *>, R> T.selectMessages(
  * @see MessageSelectBuilderUnit 查看上层 API
  */
 @SinceMirai("0.29.0")
-abstract class MessageSelectBuilder<M : MessagePacket<*, *>, R> @PublishedApi internal constructor(
+abstract class MessageSelectBuilder<M : ContactMessage, R> @PublishedApi internal constructor(
     ownerMessagePacket: M,
     stub: Any?,
     subscriber: (M.(String) -> Boolean, MessageListener<M, Any?>) -> Unit
@@ -194,7 +194,7 @@ abstract class MessageSelectBuilder<M : MessagePacket<*, *>, R> @PublishedApi in
  * @see MessageSubscribersBuilder 查看上层 API
  */
 @SinceMirai("0.29.0")
-abstract class MessageSelectBuilderUnit<M : MessagePacket<*, *>, R> @PublishedApi internal constructor(
+abstract class MessageSelectBuilderUnit<M : ContactMessage, R> @PublishedApi internal constructor(
     private val ownerMessagePacket: M,
     stub: Any?,
     subscriber: (M.(String) -> Boolean, MessageListener<M, Any?>) -> Unit
@@ -373,7 +373,7 @@ internal val SELECT_MESSAGE_STUB = Any()
 @PublishedApi
 @BuilderInference
 @OptIn(ExperimentalTypeInference::class)
-internal suspend inline fun <reified T : MessagePacket<*, *>, R> T.selectMessagesImpl(
+internal suspend inline fun <reified T : ContactMessage, R> T.selectMessagesImpl(
     timeoutMillis: Long = -1,
     isUnit: Boolean,
     @BuilderInference
@@ -465,7 +465,7 @@ internal suspend inline fun <reified T : MessagePacket<*, *>, R> T.selectMessage
 
 @Suppress("unused")
 @PublishedApi
-internal suspend inline fun <reified T : MessagePacket<*, *>> T.whileSelectMessagesImpl(
+internal suspend inline fun <reified T : ContactMessage> T.whileSelectMessagesImpl(
     timeoutMillis: Long = -1,
     crossinline selectBuilder: @MessageDsl MessageSelectBuilder<T, Boolean>.() -> Unit
 ) {

+ 5 - 5
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscribeMessages.kt

@@ -20,9 +20,9 @@ import net.mamoe.mirai.contact.isAdministrator
 import net.mamoe.mirai.contact.isOperator
 import net.mamoe.mirai.contact.isOwner
 import net.mamoe.mirai.event.events.BotEvent
+import net.mamoe.mirai.message.ContactMessage
 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 net.mamoe.mirai.message.data.first
 import net.mamoe.mirai.utils.SinceMirai
@@ -34,7 +34,7 @@ import kotlin.coroutines.EmptyCoroutineContext
 import kotlin.js.JsName
 import kotlin.jvm.JvmName
 
-typealias MessagePacketSubscribersBuilder = MessageSubscribersBuilder<MessagePacket<*, *>, Listener<MessagePacket<*, *>>, Unit, Unit>
+typealias MessagePacketSubscribersBuilder = MessageSubscribersBuilder<ContactMessage, Listener<ContactMessage>, Unit, Unit>
 
 /**
  * 订阅来自所有 [Bot] 的所有联系人的消息事件. 联系人可以是任意群或任意好友或临时会话.
@@ -53,7 +53,7 @@ fun <R> CoroutineScope.subscribeMessages(
     }
 
     return MessagePacketSubscribersBuilder(Unit)
-    { filter, messageListener: MessageListener<MessagePacket<*, *>, Unit> ->
+    { filter, messageListener: MessageListener<ContactMessage, Unit> ->
         // subscribeAlways 即注册一个监听器. 这个监听器收到消息后就传递给 [messageListener]
         // messageListener 即为 DSL 里 `contains(...) { }`, `startsWith(...) { }` 的代码块.
         subscribeAlways(coroutineContext, concurrencyKind) {
@@ -245,7 +245,7 @@ inline fun <reified E : BotEvent> Bot.incoming(
  * 消息事件的处理器.
  *
  * 注:
- * 接受者 T 为 [MessagePacket]
+ * 接受者 T 为 [ContactMessage]
  * 参数 String 为 转为字符串了的消息 ([Message.toString])
  */
 typealias MessageListener<T, R> = @MessageDsl suspend T.(String) -> R
@@ -262,7 +262,7 @@ typealias MessageListener<T, R> = @MessageDsl suspend T.(String) -> R
  */
 @Suppress("unused", "DSL_SCOPE_VIOLATION_WARNING")
 @MessageDsl
-open class MessageSubscribersBuilder<M : MessagePacket<*, *>, out Ret, R : RR, RR>(
+open class MessageSubscribersBuilder<M : ContactMessage, out Ret, R : RR, RR>(
     val stub: RR,
     /**
      * invoke 这个 lambda 时, 它将会把 [消息事件的处理器][MessageListener] 注册给事件, 并返回注册完成返回的监听器.

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

@@ -19,7 +19,7 @@ import net.mamoe.mirai.utils.unsafeWeakRef
 class FriendMessage(
     sender: QQ,
     override val message: MessageChain
-) : MessagePacket<QQ, QQ>(), BroadcastControllable {
+) : ContactMessage(), BroadcastControllable {
     override val sender: QQ by sender.unsafeWeakRef()
     override val bot: Bot get() = sender.bot
     override val subject: QQ get() = sender

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

@@ -27,7 +27,7 @@ class GroupMessage(
     val permission: MemberPermission,
     sender: Member,
     override val message: MessageChain
-) : MessagePacket<Member, Group>(), Event {
+) : ContactMessage(), Event {
     override val sender: Member by sender.unsafeWeakRef()
     val group: Group get() = sender.group
     override val bot: Bot get() = sender.bot

+ 81 - 30
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt

@@ -7,7 +7,14 @@
  * https://github.com/mamoe/mirai/blob/master/LICENSE
  */
 
-@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "unused")
+@file:Suppress(
+    "EXPERIMENTAL_UNSIGNED_LITERALS",
+    "EXPERIMENTAL_API_USAGE",
+    "unused",
+    "INVISIBLE_REFERENCE",
+    "INVISIBLE_MEMBER"
+)
+@file:OptIn(MiraiInternalAPI::class)
 
 package net.mamoe.mirai.message
 
@@ -33,20 +40,42 @@ import net.mamoe.mirai.utils.*
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
 import kotlin.jvm.JvmName
+import kotlin.jvm.JvmSynthetic
+
+/**
+ * 一条消息事件.
+ * 它是一个 [BotEvent], 因此可以被 [监听][Bot.subscribe]
+ *
+ * 支持的消息类型:
+ * [GroupMessage]
+ * [FriendMessage]
+ *
+ * @see isContextIdenticalWith 判断语境是否相同
+ */
+@Suppress("DEPRECATION")
+@SinceMirai("0.32.0")
+abstract class ContactMessage : MessagePacket<QQ, Contact>(), BotEvent
 
 /**
  * 一条从服务器接收到的消息事件.
  * 请查看各平台的 `actual` 实现的说明.
  */
-@OptIn(MiraiInternalAPI::class)
-expect abstract class MessagePacket<TSender : QQ, TSubject : Contact>() : MessagePacketBase<TSender, TSubject>
+@Suppress("DEPRECATION")
+@Deprecated(
+    message = "use ContactMessage",
+    replaceWith = ReplaceWith("ContactMessage", "net.mamoe.mirai.message.ContactMessage")
+)
+expect sealed class MessagePacket<TSender : QQ, TSubject : Contact>() : MessagePacketBase<TSender, TSubject>
 
 /**
- * 仅内部使用, 请使用 [MessagePacket]
+ * 仅内部使用, 请使用 [ContactMessage]
  */ // Tips: 在 IntelliJ 中 (左侧边栏) 打开 `Structure`, 可查看类结构
+@Deprecated(
+    message = "use ContactMessage",
+    replaceWith = ReplaceWith("ContactMessage", "net.mamoe.mirai.message.ContactMessage")
+)
 @Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST")
-@MiraiInternalAPI
-abstract class MessagePacketBase<TSender : QQ, TSubject : Contact> : Packet, BotEvent {
+abstract class MessagePacketBase<out TSender : QQ, out TSubject : Contact> : Packet, BotEvent {
     /**
      * 接受到这条消息的
      */
@@ -91,11 +120,6 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact> : Packet, Bot
     suspend inline fun reply(plain: String): MessageReceipt<TSubject> =
         subject.sendMessage(plain.toMessage().asMessageChain()) as MessageReceipt<TSubject>
 
-    @JvmName("reply1")
-    suspend inline fun String.reply(): MessageReceipt<TSubject> = reply(this)
-
-    @JvmName("reply1")
-    suspend inline fun Message.reply(): MessageReceipt<TSubject> = reply(this)
     // endregion
 
     // region 撤回
@@ -131,6 +155,7 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact> : Packet, Bot
     // endregion
 
 
+    // region 引用回复
     /**
      * 给这个消息事件的主体发送引用回复消息
      * 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息
@@ -151,12 +176,11 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact> : Packet, Bot
     @JvmName("reply2")
     suspend inline fun MessageChain.quoteReply(): MessageReceipt<TSubject> = quoteReply(this)
 
-    /**
-     * 引用这个消息
-     */
     @ExperimentalMessageSource
     inline fun MessageChain.quote(): QuoteReplyToSend = this.quote(sender)
 
+    // endregion
+
     operator fun <M : Message> get(at: Message.Key<M>): M {
         return this.message[at]
     }
@@ -189,13 +213,22 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact> : Packet, Bot
      */
     suspend inline fun Image.channel(): ByteReadChannel = bot.openChannel(this)
     // endregion
+
+
+    @Deprecated("use reply(String) for clear semantics", ReplaceWith("reply(this)"))
+    @JvmName("reply1")
+    suspend inline fun String.reply(): MessageReceipt<TSubject> = reply(this)
+
+    @Deprecated("use reply(String) for clear semantics", ReplaceWith("reply(this)"))
+    @JvmName("reply1")
+    suspend inline fun Message.reply(): MessageReceipt<TSubject> = reply(this)
 }
 
 /**
  * 判断两个 [MessagePacket] 的 [MessagePacket.sender] 和 [MessagePacket.subject] 是否相同
  */
 @SinceMirai("0.29.0")
-fun MessagePacket<*, *>.isContextIdenticalWith(another: MessagePacket<*, *>): Boolean {
+fun ContactMessage.isContextIdenticalWith(another: ContactMessage): Boolean {
     return this.sender == another.sender && this.subject == another.subject && this.bot == another.bot
 }
 
@@ -209,7 +242,8 @@ fun MessagePacket<*, *>.isContextIdenticalWith(another: MessagePacket<*, *>): Bo
  *
  * @see subscribingGet
  */
-suspend inline fun <reified P : MessagePacket<*, *>> P.nextMessage(
+@JvmSynthetic
+suspend inline fun <reified P : ContactMessage> P.nextMessage(
     timeoutMillis: Long = -1,
     crossinline filter: suspend P.(P) -> Boolean
 ): MessageChain {
@@ -229,7 +263,8 @@ suspend inline fun <reified P : MessagePacket<*, *>> P.nextMessage(
  *
  * @see subscribingGetOrNull
  */
-suspend inline fun <reified P : MessagePacket<*, *>> P.nextMessageOrNull(
+@JvmSynthetic
+suspend inline fun <reified P : ContactMessage> P.nextMessageOrNull(
     timeoutMillis: Long = -1,
     crossinline filter: suspend P.(P) -> Boolean
 ): MessageChain? {
@@ -247,7 +282,8 @@ suspend inline fun <reified P : MessagePacket<*, *>> P.nextMessageOrNull(
  *
  * @see subscribingGet
  */
-suspend inline fun <reified P : MessagePacket<*, *>> P.nextMessage(
+@JvmSynthetic
+suspend inline fun <reified P : ContactMessage> P.nextMessage(
     timeoutMillis: Long = -1
 ): MessageChain {
     return subscribingGet<P, P>(timeoutMillis) {
@@ -259,7 +295,8 @@ suspend inline fun <reified P : MessagePacket<*, *>> P.nextMessage(
  * @see nextMessage
  * @throws TimeoutCancellationException
  */
-inline fun <reified P : MessagePacket<*, *>> P.nextMessageAsync(
+@JvmSynthetic
+inline fun <reified P : ContactMessage> P.nextMessageAsync(
     timeoutMillis: Long = -1,
     coroutineContext: CoroutineContext = EmptyCoroutineContext
 ): Deferred<MessageChain> {
@@ -273,7 +310,8 @@ inline fun <reified P : MessagePacket<*, *>> P.nextMessageAsync(
 /**
  * @see nextMessage
  */
-inline fun <reified P : MessagePacket<*, *>> P.nextMessageAsync(
+@JvmSynthetic
+inline fun <reified P : ContactMessage> P.nextMessageAsync(
     timeoutMillis: Long = -1,
     coroutineContext: CoroutineContext = EmptyCoroutineContext,
     crossinline filter: suspend P.(P) -> Boolean
@@ -296,7 +334,8 @@ inline fun <reified P : MessagePacket<*, *>> P.nextMessageAsync(
  *
  * @see subscribingGetOrNull
  */
-suspend inline fun <reified P : MessagePacket<*, *>> P.nextMessageOrNull(
+@JvmSynthetic
+suspend inline fun <reified P : ContactMessage> P.nextMessageOrNull(
     timeoutMillis: Long = -1
 ): MessageChain? {
     return subscribingGetOrNull<P, P>(timeoutMillis) {
@@ -307,7 +346,8 @@ suspend inline fun <reified P : MessagePacket<*, *>> P.nextMessageOrNull(
 /**
  * @see nextMessageOrNull
  */
-inline fun <reified P : MessagePacket<*, *>> P.nextMessageOrNullAsync(
+@JvmSynthetic
+inline fun <reified P : ContactMessage> P.nextMessageOrNullAsync(
     timeoutMillis: Long = -1,
     coroutineContext: CoroutineContext = EmptyCoroutineContext
 ): Deferred<MessageChain?> {
@@ -329,21 +369,23 @@ inline fun <reified P : MessagePacket<*, *>> P.nextMessageOrNullAsync(
  * @see whileSelectMessages
  * @see selectMessages
  */
-suspend inline fun <reified M : Message> MessagePacket<*, *>.nextMessageContaining(
+@JvmSynthetic
+suspend inline fun <reified M : Message> ContactMessage.nextMessageContaining(
     timeoutMillis: Long = -1
 ): M {
-    return subscribingGet<MessagePacket<*, *>, MessagePacket<*, *>>(timeoutMillis) {
+    return subscribingGet<ContactMessage, ContactMessage>(timeoutMillis) {
         takeIf { this.isContextIdenticalWith(this@nextMessageContaining) }
     }.message.first()
 }
 
-inline fun <reified M : Message> MessagePacket<*, *>.nextMessageContainingAsync(
+@JvmSynthetic
+inline fun <reified M : Message> ContactMessage.nextMessageContainingAsync(
     timeoutMillis: Long = -1,
     coroutineContext: CoroutineContext = EmptyCoroutineContext
 ): Deferred<M> {
     return this.bot.async(coroutineContext) {
         @Suppress("RemoveExplicitTypeArguments")
-        subscribingGet<MessagePacket<*, *>, MessagePacket<*, *>>(timeoutMillis) {
+        subscribingGet<ContactMessage, ContactMessage>(timeoutMillis) {
             takeIf { this.isContextIdenticalWith(this@nextMessageContainingAsync) }
         }.message.first<M>()
     }
@@ -359,21 +401,30 @@ inline fun <reified M : Message> MessagePacket<*, *>.nextMessageContainingAsync(
  *
  * @see subscribingGetOrNull
  */
-suspend inline fun <reified M : Message> MessagePacket<*, *>.nextMessageContainingOrNull(
+@JvmSynthetic
+suspend inline fun <reified M : Message> ContactMessage.nextMessageContainingOrNull(
     timeoutMillis: Long = -1
 ): M? {
-    return subscribingGetOrNull<MessagePacket<*, *>, MessagePacket<*, *>>(timeoutMillis) {
+    return subscribingGetOrNull<ContactMessage, ContactMessage>(timeoutMillis) {
         takeIf { this.isContextIdenticalWith(this@nextMessageContainingOrNull) }
     }?.message?.first()
 }
 
-inline fun <reified M : Message> MessagePacket<*, *>.nextMessageContainingOrNullAsync(
+@JvmSynthetic
+inline fun <reified M : Message> ContactMessage.nextMessageContainingOrNullAsync(
     timeoutMillis: Long = -1,
     coroutineContext: CoroutineContext = EmptyCoroutineContext
 ): Deferred<M?> {
     return this.bot.async(coroutineContext) {
-        subscribingGetOrNull<MessagePacket<*, *>, MessagePacket<*, *>>(timeoutMillis) {
+        subscribingGetOrNull<ContactMessage, ContactMessage>(timeoutMillis) {
             takeIf { this.isContextIdenticalWith(this@nextMessageContainingOrNullAsync) }
         }?.message?.first<M>()
     }
 }
+
+
+@Suppress("DEPRECATION")
+@Deprecated(level = DeprecationLevel.HIDDEN, message = "for binary compatibility")
+fun MessagePacket<*, *>.isContextIdenticalWith(another: MessagePacket<*, *>): Boolean {
+    return (this as ContactMessage).isContextIdenticalWith(another as ContactMessage)
+}

+ 9 - 6
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt

@@ -32,7 +32,7 @@ import kotlin.jvm.JvmSynthetic
  * @see MessageReceipt.sourceSequenceId 源序列号
  * @see MessageReceipt.sourceTime 源时间
  */
-expect open class MessageReceipt<C : Contact> @OptIn(ExperimentalMessageSource::class) constructor(
+expect open class MessageReceipt<out C : Contact> @OptIn(ExperimentalMessageSource::class) constructor(
     source: MessageSource,
     target: C,
     botAsMember: Member?
@@ -100,7 +100,8 @@ expect open class MessageReceipt<C : Contact> @OptIn(ExperimentalMessageSource::
  */
 @get:JvmSynthetic
 @ExperimentalMessageSource
-inline val MessageReceipt<*>.sourceId: Long get() = this.source.id
+inline val MessageReceipt<*>.sourceId: Long
+    get() = this.source.id
 
 /**
  * 获取源消息 [MessageSource.sequenceId]
@@ -109,7 +110,8 @@ inline val MessageReceipt<*>.sourceId: Long get() = this.source.id
  */
 @get:JvmSynthetic
 @ExperimentalMessageSource
-inline val MessageReceipt<*>.sourceSequenceId: Int get() = this.source.sequenceId
+inline val MessageReceipt<*>.sourceSequenceId: Int
+    get() = this.source.sequenceId
 
 /**
  * 获取源消息 [MessageSource.time]
@@ -118,13 +120,14 @@ inline val MessageReceipt<*>.sourceSequenceId: Int get() = this.source.sequenceI
  */
 @get:JvmSynthetic
 @ExperimentalMessageSource
-inline val MessageReceipt<*>.sourceTime: Long get() = this.source.time
+inline val MessageReceipt<*>.sourceTime: Long
+    get() = this.source.time
 
-suspend inline fun MessageReceipt<out Contact>.quoteReply(message: Message) {
+suspend inline fun MessageReceipt<*>.quoteReply(message: Message) {
     return this.quoteReply(message.asMessageChain())
 }
 
-suspend inline fun MessageReceipt<out Contact>.quoteReply(message: String) {
+suspend inline fun MessageReceipt<*>.quoteReply(message: String) {
     return this.quoteReply(message.toMessage().asMessageChain())
 }
 

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

@@ -14,4 +14,4 @@ package net.mamoe.mirai.utils
  */
 @Target(AnnotationTarget.PROPERTY)
 @Retention(AnnotationRetention.BINARY)
-annotation class LazyProperty
+internal annotation class LazyProperty

+ 99 - 0
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/SoftRef.kt

@@ -0,0 +1,99 @@
+/*
+ * Copyright 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
+ */
+
+@file:Suppress("unused", "NOTHING_TO_INLINE")
+
+package net.mamoe.mirai.utils
+
+/*
+
+/**
+ * SoftRef that `getValue` for delegation throws an [IllegalStateException] if the referent is released by GC. Therefore it returns notnull value only
+ */
+class UnsafeSoftRef<T>(private val softRef: SoftRef<T>) {
+    fun get(): T = softRef.get() ?: error("SoftRef is released")
+    fun clear() = softRef.clear()
+}
+
+/**
+ * Provides delegate value.
+ *
+ * ```kotlin
+ * val bot: Bot by param.unsafeSoftRef()
+ * ```
+ */
+@JvmSynthetic
+inline operator fun <T> UnsafeSoftRef<T>.getValue(thisRef: Any?, property: KProperty<*>): T = get()
+
+/**
+ * Soft Reference.
+ * On JVM, it is implemented as a typealias referring to `SoftReference` from JDK.
+ *
+ * Details:
+ * On JVM, instances of objects are stored in the JVM Heap and are accessed via references.
+ * GC(garbage collection) can automatically collect and release the memory used by objects that are not directly referred by any other.
+ * [SoftRef] will keep the reference until JVM run out of memory.
+ *
+ * @see softRef provides a SoftRef
+ * @see unsafeSoftRef provides a UnsafeSoftRef
+ */
+@SinceMirai("0.32.0")
+expect class SoftRef<T>(referent: T) {
+    fun get(): T?
+    fun clear()
+}
+
+/**
+ * Indicates that the property is delegated by a [SoftRef]
+ *
+ * @see softRef
+ */
+@Target(AnnotationTarget.PROPERTY)
+@Retention(AnnotationRetention.SOURCE)
+annotation class SoftRefProperty
+
+/**
+ * Provides a soft reference to [this]
+ * The `getValue` for delegation returns [this] when [this] is not released by GC
+ */
+@JvmSynthetic
+inline fun <T> T.softRef(): SoftRef<T> = SoftRef(this)
+
+/**
+ * Constructs an unsafe inline delegate for [this]
+ */
+@JvmSynthetic
+inline fun <T> SoftRef<T>.unsafe(): UnsafeSoftRef<T> = UnsafeSoftRef(this)
+
+/**
+ * Provides a soft reference to [this].
+ * The `getValue` for delegation throws an [IllegalStateException] if the referent is released by GC. Therefore it returns notnull value only
+ *
+ * **UNSTABLE API**: It is strongly suggested not to use this api
+ */
+@JvmSynthetic
+inline fun <T> T.unsafeSoftRef(): UnsafeSoftRef<T> = UnsafeSoftRef(this.softRef())
+
+/**
+ * Provides delegate value.
+ *
+ * ```kotlin
+ * val bot: Bot? by param.softRef()
+ * ```
+ */
+@JvmSynthetic
+inline operator fun <T> SoftRef<T>.getValue(thisRef: Any?, property: KProperty<*>): T? = this.get()
+
+/**
+ * Call the block if the referent is absent
+ */
+@JvmSynthetic
+inline fun <T, R> SoftRef<T>.ifAbsent(block: (T) -> R): R? = this.get()?.let(block)
+
+ */

+ 1 - 1
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/Contact.kt

@@ -65,7 +65,7 @@ actual abstract class Contact : CoroutineScope, ContactJavaFriendlyAPI() {
      * @return 消息回执. 可 [引用回复][MessageReceipt.quote](仅群聊)或 [撤回][MessageReceipt.recall] 这条消息.
      */
     @JvmSynthetic //
-    actual abstract suspend fun sendMessage(message: Message): MessageReceipt<out Contact>
+    actual abstract suspend fun sendMessage(message: Message): MessageReceipt<Contact>
 
     /**
      * 上传一个图片以备发送.

+ 4 - 4
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/ContactJavaFriendlyAPI.kt

@@ -61,12 +61,12 @@ actual abstract class ContactJavaFriendlyAPI {
      */
     @Throws(EventCancelledException::class, IllegalStateException::class)
     @JvmName("sendMessage")
-    open fun __sendMessageBlockingForJava__(message: Message): MessageReceipt<out Contact> {
+    open fun __sendMessageBlockingForJava__(message: Message): MessageReceipt<Contact> {
         return runBlocking { sendMessage(message) }
     }
 
     @JvmName("sendMessage")
-    open fun __sendMessageBlockingForJava__(message: String): MessageReceipt<out Contact> {
+    open fun __sendMessageBlockingForJava__(message: String): MessageReceipt<Contact> {
         return runBlocking { sendMessage(message) }
     }
 
@@ -140,7 +140,7 @@ actual abstract class ContactJavaFriendlyAPI {
      * @see Contact.sendMessage
      */
     @JvmName("sendMessageAsync")
-    open fun __sendMessageAsyncForJava__(message: Message): Future<MessageReceipt<out Contact>> {
+    open fun __sendMessageAsyncForJava__(message: Message): Future<MessageReceipt<Contact>> {
         return future { sendMessage(message) }
     }
 
@@ -149,7 +149,7 @@ actual abstract class ContactJavaFriendlyAPI {
      * @see Contact.sendMessage
      */
     @JvmName("sendMessageAsync")
-    open fun __sendMessageAsyncForJava__(message: String): Future<MessageReceipt<out Contact>> {
+    open fun __sendMessageAsyncForJava__(message: String): Future<MessageReceipt<Contact>> {
         return future { sendMessage(message) }
     }
 

+ 1 - 1
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/QQ.kt

@@ -90,7 +90,7 @@ actual abstract class QQ : Contact(), CoroutineScope {
      * @return 消息回执. 可进行撤回 ([MessageReceipt.recall])
      */
     @JvmSynthetic
-    actual abstract override suspend fun sendMessage(message: Message): MessageReceipt<out QQ>
+    actual abstract override suspend fun sendMessage(message: Message): MessageReceipt<QQ>
 
     /**
      * 上传一个图片以备发送.

+ 6 - 1
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt

@@ -32,8 +32,13 @@ import java.net.URL
  * 一条从服务器接收到的消息事件.
  * JVM 平台相关扩展
  */
+@Suppress("DEPRECATION")
+@Deprecated(
+    message = "use ContactMessage",
+    replaceWith = ReplaceWith("ContactMessage", "net.mamoe.mirai.message.ContactMessage")
+)
 @OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class)
-actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual constructor() : MessagePacketBase<TSender, TSubject>() {
+actual sealed class MessagePacket<TSender : QQ, TSubject : Contact> actual constructor() : MessagePacketBase<TSender, TSubject>() {
     // region 上传图片
     suspend inline fun uploadImage(image: BufferedImage): Image = subject.uploadImage(image)
 

+ 1 - 1
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessageReceipt.kt

@@ -29,7 +29,7 @@ import net.mamoe.mirai.utils.unsafeWeakRef
  */
 @Suppress("FunctionName")
 @OptIn(MiraiInternalAPI::class)
-actual open class MessageReceipt<C : Contact> @OptIn(ExperimentalMessageSource::class)
+actual open class MessageReceipt<out C : Contact> @OptIn(ExperimentalMessageSource::class)
 actual constructor(
     actual val source: MessageSource,
     target: C,