Forráskód Böngészése

- Add FileMessage.internalId hence support serialization
- Add FileMessage.Serializer
- Add FileMessage.create and IMirai.createFileMessage to construct manually
- Mark FileMessage as stable
- Move TestMiraiCode from mirai-core-api to mirai-core

fix #1082

Him188 4 éve
szülő
commit
af58c163d2

+ 15 - 1
binary-compatibility-validator/android/api/binary-compatibility-validator-android.api

@@ -88,6 +88,7 @@ public abstract interface class net/mamoe/mirai/IMirai : net/mamoe/mirai/LowLeve
 	public fun calculateGroupCodeByGroupUin (J)J
 	public fun calculateGroupUinByGroupCode (J)J
 	public abstract fun constructMessageSource (JLnet/mamoe/mirai/message/data/MessageSourceKind;JJ[II[ILnet/mamoe/mirai/message/data/MessageChain;)Lnet/mamoe/mirai/message/data/OfflineMessageSource;
+	public abstract fun createFileMessage (Ljava/lang/String;ILjava/lang/String;J)Lnet/mamoe/mirai/message/data/FileMessage;
 	public abstract fun createImage (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image;
 	public fun downloadForwardMessage (Lnet/mamoe/mirai/Bot;Ljava/lang/String;)Ljava/util/List;
 	public abstract fun downloadForwardMessage (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
@@ -4046,11 +4047,13 @@ public final class net/mamoe/mirai/message/data/Face$Companion {
 	public final fun serializer ()Lkotlinx/serialization/KSerializer;
 }
 
-public abstract interface class net/mamoe/mirai/message/data/FileMessage : net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageContent {
+public abstract interface class net/mamoe/mirai/message/data/FileMessage : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageContent {
 	public static final field Key Lnet/mamoe/mirai/message/data/FileMessage$Key;
 	public static final field SERIAL_NAME Ljava/lang/String;
 	public fun contentToString ()Ljava/lang/String;
+	public static fun create (Ljava/lang/String;ILjava/lang/String;J)Lnet/mamoe/mirai/message/data/FileMessage;
 	public abstract fun getId ()Ljava/lang/String;
+	public abstract fun getInternalId ()I
 	public fun getKey ()Lnet/mamoe/mirai/message/data/FileMessage$Key;
 	public synthetic fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey;
 	public abstract fun getName ()Ljava/lang/String;
@@ -4061,6 +4064,16 @@ public abstract interface class net/mamoe/mirai/message/data/FileMessage : net/m
 
 public final class net/mamoe/mirai/message/data/FileMessage$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
 	public static final field SERIAL_NAME Ljava/lang/String;
+	public final fun create (Ljava/lang/String;ILjava/lang/String;J)Lnet/mamoe/mirai/message/data/FileMessage;
+}
+
+public final class net/mamoe/mirai/message/data/FileMessage$Serializer : kotlinx/serialization/KSerializer {
+	public static final field INSTANCE Lnet/mamoe/mirai/message/data/FileMessage$Serializer;
+	public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+	public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/FileMessage;
+	public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+	public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+	public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lnet/mamoe/mirai/message/data/FileMessage;)V
 }
 
 public final class net/mamoe/mirai/message/data/FlashImage : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/HummerMessage, net/mamoe/mirai/message/data/MessageContent {
@@ -4689,6 +4702,7 @@ public final class net/mamoe/mirai/message/data/MessageSourceKind$Companion {
 
 public final class net/mamoe/mirai/message/data/MessageUtils {
 	public static final synthetic fun At (Lnet/mamoe/mirai/contact/UserOrBot;)Lnet/mamoe/mirai/message/data/At;
+	public static final synthetic fun FileMessage (Ljava/lang/String;ILjava/lang/String;J)Lnet/mamoe/mirai/message/data/FileMessage;
 	public static final synthetic fun Image (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image;
 	public static final synthetic fun at (Lnet/mamoe/mirai/contact/Member;)Lnet/mamoe/mirai/message/data/At;
 	public static final synthetic fun buildMessageChain (ILkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/MessageChain;

+ 15 - 1
binary-compatibility-validator/api/binary-compatibility-validator.api

@@ -88,6 +88,7 @@ public abstract interface class net/mamoe/mirai/IMirai : net/mamoe/mirai/LowLeve
 	public fun calculateGroupCodeByGroupUin (J)J
 	public fun calculateGroupUinByGroupCode (J)J
 	public abstract fun constructMessageSource (JLnet/mamoe/mirai/message/data/MessageSourceKind;JJ[II[ILnet/mamoe/mirai/message/data/MessageChain;)Lnet/mamoe/mirai/message/data/OfflineMessageSource;
+	public abstract fun createFileMessage (Ljava/lang/String;ILjava/lang/String;J)Lnet/mamoe/mirai/message/data/FileMessage;
 	public abstract fun createImage (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image;
 	public fun downloadForwardMessage (Lnet/mamoe/mirai/Bot;Ljava/lang/String;)Ljava/util/List;
 	public abstract fun downloadForwardMessage (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
@@ -4046,11 +4047,13 @@ public final class net/mamoe/mirai/message/data/Face$Companion {
 	public final fun serializer ()Lkotlinx/serialization/KSerializer;
 }
 
-public abstract interface class net/mamoe/mirai/message/data/FileMessage : net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageContent {
+public abstract interface class net/mamoe/mirai/message/data/FileMessage : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageContent {
 	public static final field Key Lnet/mamoe/mirai/message/data/FileMessage$Key;
 	public static final field SERIAL_NAME Ljava/lang/String;
 	public fun contentToString ()Ljava/lang/String;
+	public static fun create (Ljava/lang/String;ILjava/lang/String;J)Lnet/mamoe/mirai/message/data/FileMessage;
 	public abstract fun getId ()Ljava/lang/String;
+	public abstract fun getInternalId ()I
 	public fun getKey ()Lnet/mamoe/mirai/message/data/FileMessage$Key;
 	public synthetic fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey;
 	public abstract fun getName ()Ljava/lang/String;
@@ -4061,6 +4064,16 @@ public abstract interface class net/mamoe/mirai/message/data/FileMessage : net/m
 
 public final class net/mamoe/mirai/message/data/FileMessage$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
 	public static final field SERIAL_NAME Ljava/lang/String;
+	public final fun create (Ljava/lang/String;ILjava/lang/String;J)Lnet/mamoe/mirai/message/data/FileMessage;
+}
+
+public final class net/mamoe/mirai/message/data/FileMessage$Serializer : kotlinx/serialization/KSerializer {
+	public static final field INSTANCE Lnet/mamoe/mirai/message/data/FileMessage$Serializer;
+	public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+	public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/FileMessage;
+	public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+	public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+	public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lnet/mamoe/mirai/message/data/FileMessage;)V
 }
 
 public final class net/mamoe/mirai/message/data/FlashImage : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/HummerMessage, net/mamoe/mirai/message/data/MessageContent {
@@ -4689,6 +4702,7 @@ public final class net/mamoe/mirai/message/data/MessageSourceKind$Companion {
 
 public final class net/mamoe/mirai/message/data/MessageUtils {
 	public static final synthetic fun At (Lnet/mamoe/mirai/contact/UserOrBot;)Lnet/mamoe/mirai/message/data/At;
+	public static final synthetic fun FileMessage (Ljava/lang/String;ILjava/lang/String;J)Lnet/mamoe/mirai/message/data/FileMessage;
 	public static final synthetic fun Image (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image;
 	public static final synthetic fun at (Lnet/mamoe/mirai/contact/Member;)Lnet/mamoe/mirai/message/data/At;
 	public static final synthetic fun buildMessageChain (ILkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/MessageChain;

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

@@ -137,6 +137,12 @@ public interface IMirai : LowLevelApiAccessor {
      */
     public fun createImage(imageId: String): Image
 
+    /**
+     * 创建一个 [FileMessage]. [name] 与 [size] 只供本地使用, 发送消息时只会使用 [id] 和 [internalId].
+     * @since 2.5
+     */
+    public fun createFileMessage(id: String, internalId: Int, name: String, size: Long): FileMessage
+
     /**
      * 获取图片下载链接
      *

+ 3 - 0
mirai-core-api/src/commonMain/kotlin/internal/message/MessageSerializersImpl.kt

@@ -27,6 +27,9 @@ import kotlin.reflect.KClass
 import kotlin.reflect.full.allSuperclasses
 import kotlin.reflect.full.isSubclassOf
 
+internal fun SerialDescriptor.copy(newName: String): SerialDescriptor =
+    buildClassSerialDescriptor(newName) { takeElementsFrom(this@copy) }
+
 
 internal fun ClassSerialDescriptorBuilder.takeElementsFrom(descriptor: SerialDescriptor) {
     with(descriptor) {

+ 1 - 9
mirai-core-api/src/commonMain/kotlin/message/code/CodableMessage.kt

@@ -10,7 +10,7 @@
 package net.mamoe.mirai.message.code
 
 import net.mamoe.mirai.message.code.MiraiCode.deserializeMiraiCode
-import net.mamoe.mirai.message.data.*
+import net.mamoe.mirai.message.data.Message
 import net.mamoe.mirai.utils.MiraiExperimentalApi
 
 
@@ -19,14 +19,6 @@ import net.mamoe.mirai.utils.MiraiExperimentalApi
  *
  * 从字符串解析 mirai 码:[MiraiCode.deserializeMiraiCode]
  *
- * @see At
- * @see AtAll
- * @see VipFace
- * @see Face
- * @see Image
- * @see FlashImage
- * @see PokeMessage
- *
  * @see MiraiCode
  */
 public interface CodableMessage : Message {

+ 3 - 0
mirai-core-api/src/commonMain/kotlin/message/code/internal/impl.kt

@@ -130,6 +130,9 @@ private object MiraiCodeParsers: AbstractMap<String, MiraiCodeParser>(), Map<Str
 
         MusicShare(MusicKind.valueOf(kind), title, summary, jumpUrl, pictureUrl, musicUrl, brief)
     },
+    "file" to MiraiCodeParser(Regex("""(.*?),(.*?),(.*?),(.*?)""")) { (id, internalId, name, size) ->
+        FileMessage(id, internalId.toInt(), name, size.toLong())
+    },
 )
 
 

+ 79 - 7
mirai-core-api/src/commonMain/kotlin/message/data/FileMessage.kt

@@ -7,27 +7,61 @@
  *  https://github.com/mamoe/mirai/blob/master/LICENSE
  */
 
+@file:Suppress("NOTHING_TO_INLINE")
+@file:JvmMultifileClass
+@file:JvmName("MessageUtils")
 
 package net.mamoe.mirai.message.data
 
+import kotlinx.serialization.KSerializer
 import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
 import net.mamoe.kjbb.JvmBlockingBridge
+import net.mamoe.mirai.Mirai
 import net.mamoe.mirai.contact.FileSupported
+import net.mamoe.mirai.event.events.MessageEvent
+import net.mamoe.mirai.internal.message.copy
+import net.mamoe.mirai.internal.message.map
+import net.mamoe.mirai.message.code.CodableMessage
 import net.mamoe.mirai.utils.MiraiExperimentalApi
+import net.mamoe.mirai.utils.MiraiInternalApi
 import net.mamoe.mirai.utils.RemoteFile
 import net.mamoe.mirai.utils.safeCast
 
 /**
  * 文件消息.
  *
+ * [name] 与 [size] 只供本地使用, 发送消息时只会使用 [id] 和 [internalId].
+ *
+ * ### 文件操作
+ * 要下载这个文件, 可通过 [toRemoteFile] 获取到 [RemoteFile] 然后操作.
+ *
+ * 要获取到 [FileMessage], 可以通过 [MessageEvent.message] 获取, 或通过 [RemoteFile.upload] 上传.
+ *
  * @since 2.5
- * @suppress 文件消息不稳定, 可能在未来版本有不兼容变更.
+ * @suppress [FileMessage] 的使用是稳定的, 但自行实现不稳定.
  */
+@Serializable(FileMessage.Serializer::class)
 @SerialName(FileMessage.SERIAL_NAME)
-@MiraiExperimentalApi
-public interface FileMessage : MessageContent, ConstrainSingle {
-    public val name: String
+public interface FileMessage : MessageContent, ConstrainSingle, CodableMessage {
+    /**
+     * 服务器需要的某种 ID.
+     */
     public val id: String
+
+    /**
+     * 服务器需要的某种 ID.
+     */
+    public val internalId: Int
+
+    /**
+     * 文件名
+     */
+    public val name: String
+
+    /**
+     * 文件大小 bytes
+     */
     public val size: Long
 
     override fun contentToString(): String = "[文件]$name" // orthodox
@@ -35,7 +69,6 @@ public interface FileMessage : MessageContent, ConstrainSingle {
     /**
      * 获取一个对应的 [RemoteFile]. 当目标群或好友不存在这个文件时返回 `null`.
      */
-    @MiraiExperimentalApi
     @JvmBlockingBridge
     public suspend fun toRemoteFile(contact: FileSupported): RemoteFile? {
         return contact.filesRoot.resolveById(id)
@@ -43,8 +76,47 @@ public interface FileMessage : MessageContent, ConstrainSingle {
 
     override val key: Key get() = Key
 
+    /**
+     * 注意, baseKey [MessageContent] 不稳定. 未来可能会有变更.
+     */
     public companion object Key :
-        AbstractPolymorphicMessageKey<MessageContent, ForwardMessage>(MessageContent, { it.safeCast() }) {
+        AbstractPolymorphicMessageKey<@MiraiExperimentalApi MessageContent, ForwardMessage>(
+            MessageContent, { it.safeCast() }) {
+
         public const val SERIAL_NAME: String = "FileMessage"
+
+        /**
+         * 构造 [FileMessage]
+         * @since 2.5
+         */
+        @JvmStatic
+        public fun create(id: String, internalId: Int, name: String, size: Long): FileMessage =
+            Mirai.createFileMessage(id, internalId, name, size)
     }
-}
+
+    public object Serializer : KSerializer<FileMessage> by FallbackSerializer("FileMessage") // not polymorphic
+
+    @MiraiInternalApi
+    private open class FallbackSerializer(serialName: String) : KSerializer<FileMessage> by Delegate.serializer().map(
+        Delegate.serializer().descriptor.copy(serialName),
+        serialize = { Delegate(id, internalId, name, size) },
+        deserialize = { Mirai.createFileMessage(id, internalId, name, size) },
+    ) {
+        @SerialName(Image.SERIAL_NAME)
+        @Serializable
+        data class Delegate(
+            val id: String,
+            val internalId: Int,
+            val name: String,
+            val size: Long,
+        )
+    }
+}
+
+/**
+ * 构造 [FileMessage]
+ * @since 2.5
+ */
+@JvmSynthetic
+public inline fun FileMessage(id: String, internalId: Int, name: String, size: Long): FileMessage =
+    FileMessage.create(id, internalId, name, size)

+ 4 - 1
mirai-core/src/commonMain/kotlin/MiraiImpl.kt

@@ -23,7 +23,6 @@ import kotlinx.serialization.json.*
 import net.mamoe.mirai.*
 import net.mamoe.mirai.contact.*
 import net.mamoe.mirai.data.*
-import net.mamoe.mirai.event.broadcast
 import net.mamoe.mirai.event.events.*
 import net.mamoe.mirai.internal.contact.*
 import net.mamoe.mirai.internal.contact.info.FriendInfoImpl
@@ -900,6 +899,10 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
         }
     }
 
+    override fun createFileMessage(id: String, internalId: Int, name: String, size: Long): FileMessage {
+        return FileMessageImpl(id, internalId, name, size)
+    }
+
     @Suppress("DEPRECATION", "OverridingDeprecatedMember")
     override suspend fun queryImageUrl(bot: Bot, image: Image): String = when (image) {
         is ConstOriginUrlAware -> image.originUrl

+ 13 - 25
mirai-core/src/commonMain/kotlin/message/FileMessageImpl.kt

@@ -22,34 +22,22 @@ internal fun FileMessage.checkIsImpl(): FileMessageImpl {
 
 @Serializable
 @SerialName(FileMessage.SERIAL_NAME)
-internal class FileMessageImpl(
-    override val name: String,
+internal data class FileMessageImpl(
     override val id: String,
+    @SerialName("internalId") val busId: Int,
+    override val name: String,
     override val size: Long,
-    val busId: Int // internal // TODO: 2021/3/8 introduce OnlineFileMessage and OfflineFileMessage to eliminate property `busId`.
 ) : FileMessage {
-    override fun toString(): String = "[mirai:file:$name,$id]"
-
-    @Suppress("DuplicatedCode")
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (javaClass != other?.javaClass) return false
-
-        other as FileMessageImpl
-
-        if (name != other.name) return false
-        if (id != other.id) return false
-        if (size != other.size) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = name.hashCode()
-        result = 31 * result + id.hashCode()
-        result = 31 * result + size.hashCode()
-        result = 31 * result + busId.hashCode()
-        return result
+    override val internalId: Int
+        get() = busId
+
+    override fun appendMiraiCodeTo(builder: StringBuilder) {
+        builder.append("[mirai:file:")
+        builder.append(id).append(",")
+        builder.append(busId).append(",")
+        builder.append(name).append(",")
+        builder.append(size).append("]")
     }
 
+    override fun toString(): String = "[mirai:file:$name,$id,$size,$busId]"
 }

+ 3 - 3
mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt

@@ -376,10 +376,10 @@ private object ReceiveMessageTransformer {
 
                         list.add(
                             FileMessageImpl(
+                                id = file.filePath,
+                                busId = file.busId, // path i.e. /a99e95fa-7b2d-11eb-adae-5452007b698a
                                 name = file.fileName,
-                                id = file.filePath, // path i.e. /a99e95fa-7b2d-11eb-adae-5452007b698a
-                                size = file.fileSize,
-                                busId = file.busId
+                                size = file.fileSize
                             )
                         )
                     }

+ 2 - 2
mirai-core/src/commonMain/kotlin/utils/RemoteFileImpl.kt

@@ -507,7 +507,7 @@ internal class RemoteFileImpl(
     ): FileMessage {
         val resp = upload0(resource, null) ?: error("Failed to upload file.")
         return FileMessageImpl(
-            name, resp.fileId, resource.size, resp.busId
+            resp.fileId, resp.busId, name, resource.size
         )
     }
 
@@ -544,6 +544,6 @@ internal class RemoteFileImpl(
     override suspend fun toMessage(): FileMessage? {
         val info = getFileFolderInfo() ?: return null
         if (!info.isFile) return null
-        return FileMessageImpl(name, info.id, info.size, info.busId)
+        return FileMessageImpl(info.id, info.busId, name, info.size)
     }
 }

+ 10 - 2
mirai-core-api/src/commonTest/kotlin/message/code/TestMiraiCode.kt → mirai-core/src/commonTest/kotlin/message/code/TestMiraiCode.kt

@@ -7,7 +7,9 @@
  *  https://github.com/mamoe/mirai/blob/master/LICENSE
  */
 
-package net.mamoe.mirai.message.code
+@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // MiraiCodeParser
+
+package net.mamoe.mirai.internal.message.code
 
 import net.mamoe.mirai.message.code.MiraiCode.deserializeMiraiCode
 import net.mamoe.mirai.message.code.internal.MiraiCodeParser
@@ -32,7 +34,12 @@ class TestMiraiCode {
     }
 
     @Test
-    fun testCodes() {
+    fun `test serialization`() {
+        assertEquals("[mirai:file:id,1,name,2]", FileMessage("id", 1, "name", 2).serializeToMiraiCode())
+    }
+
+    @Test
+    fun `test deserialization`() {
         assertEquals(AtAll.toMessageChain(), "[mirai:atall]".deserializeMiraiCode())
         assertEquals(PlainText("[Hello").toMessageChain(), "\\[Hello".deserializeMiraiCode())
         assertEquals(buildMessageChain {
@@ -62,6 +69,7 @@ class TestMiraiCode {
         assertEquals(buildMessageChain {
             +Dice(1)
         }, "[mirai:dice:1]".deserializeMiraiCode())
+        assertEquals(FileMessage("id", 1, "name", 2), "[mirai:file:id,1,name,2]".deserializeMiraiCode().single())
 
         val musicShare = MusicShare(
             kind = MusicKind.NeteaseCloudMusic,

+ 14 - 0
mirai-core/src/jvmTest/kotlin/message/data/MessageSerializationTest.kt

@@ -17,6 +17,7 @@ import kotlinx.serialization.json.JsonObject
 import kotlinx.serialization.json.JsonPrimitive
 import kotlinx.serialization.serializer
 import net.mamoe.mirai.Mirai
+import net.mamoe.mirai.internal.message.FileMessageImpl
 import net.mamoe.mirai.internal.message.MarketFaceImpl
 import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
 import net.mamoe.mirai.message.MessageSerializers
@@ -111,6 +112,7 @@ internal class MessageSerializationTest {
         RichMessageOrigin(SimpleServiceMessage(1, "content"), "resource id", RichMessageKind.LONG),
         ShowImageFlag,
         Dice(1),
+        FileMessageImpl("id", 2, "name", 1)
     )
 
     companion object {
@@ -121,6 +123,18 @@ internal class MessageSerializationTest {
         }
     }
 
+    @Test
+    fun `test FileMessage serialization`() {
+        @Serializable
+        data class W(
+            val m: FileMessage
+        )
+
+        val w = W(FileMessageImpl("id", 2, "name", 1))
+        println(w.serialize(W.serializer()))
+        assertEquals(w, w.serialize(W.serializer()).deserialize(W.serializer()))
+    }
+
     @Test
     fun `test polymorphic serialization`() {
         @Serializable