diff --git a/build.gradle.kts b/build.gradle.kts index 162ce571..d81c69f6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -84,6 +84,7 @@ apiValidation { "love.forte.simbot.annotations.InternalSimbotAPI", "love.forte.simbot.qguild.QGInternalApi", "love.forte.simbot.component.qguild.ExperimentalQGApi", + "love.forte.simbot.qguild.ExperimentalQGMediaApi" ), ) diff --git a/simbot-component-qq-guild-api/api/simbot-component-qq-guild-api.api b/simbot-component-qq-guild-api/api/simbot-component-qq-guild-api.api index 8ab37d67..9b1d8714 100644 --- a/simbot-component-qq-guild-api/api/simbot-component-qq-guild-api.api +++ b/simbot-component-qq-guild-api/api/simbot-component-qq-guild-api.api @@ -33,6 +33,9 @@ public final class love/forte/simbot/qguild/ErrInfo$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public abstract interface annotation class love/forte/simbot/qguild/ExperimentalQGMediaApi : java/lang/annotation/Annotation { +} + public abstract interface annotation class love/forte/simbot/qguild/Generated : java/lang/annotation/Annotation { } @@ -1032,10 +1035,11 @@ public final class love/forte/simbot/qguild/api/files/UploadGroupFilesApi : love public static final field FILE_TYPE_IMAGE I public static final field FILE_TYPE_VIDEO I public static final field Factory Llove/forte/simbot/qguild/api/files/UploadGroupFilesApi$Factory; - public synthetic fun (Ljava/lang/String;Llove/forte/simbot/qguild/api/files/UploadGroupFilesApi$Body;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Llove/forte/simbot/qguild/api/files/UploadGroupFilesApi$BodyValue;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public static final fun create (Ljava/lang/String;ILjava/lang/String;)Llove/forte/simbot/qguild/api/files/UploadGroupFilesApi; public static final fun create (Ljava/lang/String;ILjava/lang/String;Z)Llove/forte/simbot/qguild/api/files/UploadGroupFilesApi; public static final fun create (Ljava/lang/String;Llove/forte/simbot/qguild/api/files/UploadGroupFilesApi$Body;)Llove/forte/simbot/qguild/api/files/UploadGroupFilesApi; + public static final fun create (Ljava/lang/String;Llove/forte/simbot/qguild/api/files/UploadGroupFilesApi$BodyValue;)Llove/forte/simbot/qguild/api/files/UploadGroupFilesApi; public fun getResultDeserializationStrategy ()Lkotlinx/serialization/DeserializationStrategy; } @@ -1070,10 +1074,38 @@ public final class love/forte/simbot/qguild/api/files/UploadGroupFilesApi$Body$C public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class love/forte/simbot/qguild/api/files/UploadGroupFilesApi$BodyValue { + public static final field Companion Llove/forte/simbot/qguild/api/files/UploadGroupFilesApi$BodyValue$Companion; + public fun ()V + public final fun getFileType ()Ljava/lang/Integer; + public final fun getSrvSendMsg ()Ljava/lang/Boolean; + public final fun getUrl ()Ljava/lang/String; + public final fun setFileType (Ljava/lang/Integer;)V + public final fun setSrvSendMsg (Ljava/lang/Boolean;)V + public final fun setUrl (Ljava/lang/String;)V + public fun toString ()Ljava/lang/String; +} + +public synthetic class love/forte/simbot/qguild/api/files/UploadGroupFilesApi$BodyValue$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Llove/forte/simbot/qguild/api/files/UploadGroupFilesApi$BodyValue$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Llove/forte/simbot/qguild/api/files/UploadGroupFilesApi$BodyValue; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Llove/forte/simbot/qguild/api/files/UploadGroupFilesApi$BodyValue;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class love/forte/simbot/qguild/api/files/UploadGroupFilesApi$BodyValue$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class love/forte/simbot/qguild/api/files/UploadGroupFilesApi$Factory : love/forte/simbot/qguild/api/SimplePostApiDescription { public final fun create (Ljava/lang/String;ILjava/lang/String;)Llove/forte/simbot/qguild/api/files/UploadGroupFilesApi; public final fun create (Ljava/lang/String;ILjava/lang/String;Z)Llove/forte/simbot/qguild/api/files/UploadGroupFilesApi; public final fun create (Ljava/lang/String;Llove/forte/simbot/qguild/api/files/UploadGroupFilesApi$Body;)Llove/forte/simbot/qguild/api/files/UploadGroupFilesApi; + public final fun create (Ljava/lang/String;Llove/forte/simbot/qguild/api/files/UploadGroupFilesApi$BodyValue;)Llove/forte/simbot/qguild/api/files/UploadGroupFilesApi; public static synthetic fun create$default (Llove/forte/simbot/qguild/api/files/UploadGroupFilesApi$Factory;Ljava/lang/String;ILjava/lang/String;ZILjava/lang/Object;)Llove/forte/simbot/qguild/api/files/UploadGroupFilesApi; } @@ -1083,10 +1115,11 @@ public final class love/forte/simbot/qguild/api/files/UploadUserFilesApi : love/ public static final field FILE_TYPE_IMAGE I public static final field FILE_TYPE_VIDEO I public static final field Factory Llove/forte/simbot/qguild/api/files/UploadUserFilesApi$Factory; - public synthetic fun (Ljava/lang/String;Llove/forte/simbot/qguild/api/files/UploadUserFilesApi$Body;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Llove/forte/simbot/qguild/api/files/UploadUserFilesApi$BodyValue;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public static final fun create (Ljava/lang/String;ILjava/lang/String;)Llove/forte/simbot/qguild/api/files/UploadUserFilesApi; public static final fun create (Ljava/lang/String;ILjava/lang/String;Z)Llove/forte/simbot/qguild/api/files/UploadUserFilesApi; public static final fun create (Ljava/lang/String;Llove/forte/simbot/qguild/api/files/UploadUserFilesApi$Body;)Llove/forte/simbot/qguild/api/files/UploadUserFilesApi; + public static final fun create (Ljava/lang/String;Llove/forte/simbot/qguild/api/files/UploadUserFilesApi$BodyValue;)Llove/forte/simbot/qguild/api/files/UploadUserFilesApi; public fun getResultDeserializationStrategy ()Lkotlinx/serialization/DeserializationStrategy; } @@ -1121,10 +1154,38 @@ public final class love/forte/simbot/qguild/api/files/UploadUserFilesApi$Body$Co public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class love/forte/simbot/qguild/api/files/UploadUserFilesApi$BodyValue { + public static final field Companion Llove/forte/simbot/qguild/api/files/UploadUserFilesApi$BodyValue$Companion; + public fun ()V + public final fun getFileType ()Ljava/lang/Integer; + public final fun getSrvSendMsg ()Ljava/lang/Boolean; + public final fun getUrl ()Ljava/lang/String; + public final fun setFileType (Ljava/lang/Integer;)V + public final fun setSrvSendMsg (Ljava/lang/Boolean;)V + public final fun setUrl (Ljava/lang/String;)V + public fun toString ()Ljava/lang/String; +} + +public synthetic class love/forte/simbot/qguild/api/files/UploadUserFilesApi$BodyValue$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Llove/forte/simbot/qguild/api/files/UploadUserFilesApi$BodyValue$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Llove/forte/simbot/qguild/api/files/UploadUserFilesApi$BodyValue; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Llove/forte/simbot/qguild/api/files/UploadUserFilesApi$BodyValue;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class love/forte/simbot/qguild/api/files/UploadUserFilesApi$BodyValue$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class love/forte/simbot/qguild/api/files/UploadUserFilesApi$Factory : love/forte/simbot/qguild/api/SimplePostApiDescription { public final fun create (Ljava/lang/String;ILjava/lang/String;)Llove/forte/simbot/qguild/api/files/UploadUserFilesApi; public final fun create (Ljava/lang/String;ILjava/lang/String;Z)Llove/forte/simbot/qguild/api/files/UploadUserFilesApi; public final fun create (Ljava/lang/String;Llove/forte/simbot/qguild/api/files/UploadUserFilesApi$Body;)Llove/forte/simbot/qguild/api/files/UploadUserFilesApi; + public final fun create (Ljava/lang/String;Llove/forte/simbot/qguild/api/files/UploadUserFilesApi$BodyValue;)Llove/forte/simbot/qguild/api/files/UploadUserFilesApi; public static synthetic fun create$default (Llove/forte/simbot/qguild/api/files/UploadUserFilesApi$Factory;Ljava/lang/String;ILjava/lang/String;ZILjava/lang/Object;)Llove/forte/simbot/qguild/api/files/UploadUserFilesApi; } diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/OptAnnotations.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/OptAnnotations.kt index eb284b8c..c2d00693 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/OptAnnotations.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/OptAnnotations.kt @@ -18,7 +18,6 @@ package love.forte.simbot.qguild - /** * 一个仅服务于Java的API。对于Kotlin来讲通常有更优选择。 */ @@ -26,6 +25,7 @@ package love.forte.simbot.qguild @MustBeDocumented @RequiresOptIn("API marked for Java use, not recommended for Kotlin.", level = RequiresOptIn.Level.WARNING) public annotation class QGApi4J + /** * 一个仅服务于JS的API。对于Kotlin来讲通常有更优选择。 */ @@ -50,3 +50,16 @@ public annotation class QGInternalApi @Retention(AnnotationRetention.SOURCE) @MustBeDocumented public annotation class Generated + +/** + * 一个**实验性**的与媒体资源相关的API,可能在未来发生变更或被移除。 + * + * @since 4.1.1 + */ +@Retention(AnnotationRetention.BINARY) +@MustBeDocumented +@RequiresOptIn( + "一个实验性的与媒体资源相关的API,可能在未来发生变更或被移除。", + level = RequiresOptIn.Level.WARNING +) +public annotation class ExperimentalQGMediaApi diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/api/files/UploadGroupFilesApi.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/api/files/UploadGroupFilesApi.kt index 7ec15fad..aaecab61 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/api/files/UploadGroupFilesApi.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/api/files/UploadGroupFilesApi.kt @@ -20,9 +20,13 @@ package love.forte.simbot.qguild.api.files import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import love.forte.simbot.qguild.ExperimentalQGMediaApi import love.forte.simbot.qguild.api.PostQQGuildApi import love.forte.simbot.qguild.api.SimplePostApiDescription import love.forte.simbot.qguild.model.MessageMedia +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmStatic @@ -34,7 +38,7 @@ import kotlin.jvm.JvmStatic */ public class UploadGroupFilesApi private constructor( openid: String, - private val _body: Body + private val _body: BodyValue ) : PostQQGuildApi() { public companion object Factory : SimplePostApiDescription( "/v2/groups/{group_openid}/files" @@ -50,9 +54,28 @@ public class UploadGroupFilesApi private constructor( * @param openid 群聊的 openid */ @JvmStatic + @Suppress("DEPRECATION") + @Deprecated("Use create(openid: String, body: BodyValue)") public fun create(openid: String, body: Body): UploadGroupFilesApi = + UploadGroupFilesApi( + openid, + BodyValue().also { + it.fileType = body.fileType + it.url = body.url + it.srvSendMsg = body.srvSendMsg + }) + + + /** + * Create [UploadGroupFilesApi]. + * + * @param openid 群聊的 openid + */ + @JvmStatic + public fun create(openid: String, body: BodyValue): UploadGroupFilesApi = UploadGroupFilesApi(openid, body) + /** * Create [UploadGroupFilesApi]. * @@ -73,13 +96,42 @@ public class UploadGroupFilesApi private constructor( ): UploadGroupFilesApi = create( openid, - Body( - fileType = fileType, - url = url, - srvSendMsg = srvSendMsg - ) + BodyValue().also { + it.fileType = fileType + it.url = url + it.srvSendMsg = srvSendMsg + } ) + /** + * Create [UploadGroupFilesApi]. + * + * @param openid 群聊的 openid + * @param fileType 媒体类型:1 图片,2 视频,3 语音,4 文件(暂不开放) + * 资源格式要求: + * 图片:png/jpg,视频:mp4,语音:silk + * @param fileData 上传文件的数据 + * @param srvSendMsg 设置 true 会直接发送消息到目标端,且会占用主动消息频次 + * + * @since 4.1.1 + */ + @ExperimentalQGMediaApi + @JvmStatic + @JvmOverloads + public fun create( + openid: String, + fileType: Int, + fileData: ByteArray, + srvSendMsg: Boolean = false + ): UploadGroupFilesApi = + create( + openid, + BodyValue().also { + it.fileType = fileType + it.fileDataBytes = fileData + it.srvSendMsg = srvSendMsg + } + ) } override val resultDeserializationStrategy: DeserializationStrategy @@ -87,7 +139,16 @@ public class UploadGroupFilesApi private constructor( override val path: Array = arrayOf("v2", "groups", openid, "files") - override fun createBody(): Any = _body + @OptIn(ExperimentalQGMediaApi::class) + private val fileDataBytes = _body.fileDataBytes + + @OptIn(ExperimentalEncodingApi::class) + override fun createBody(): Any { + if (fileDataBytes != null) { + _body.fileDataBase64Hex = Base64.encode(fileDataBytes) + } + return _body + } /** * @property fileType 媒体类型:1 图片,2 视频,3 语音,4 文件(暂不开放) @@ -97,6 +158,7 @@ public class UploadGroupFilesApi private constructor( * @property srvSendMsg 设置 true 会直接发送消息到目标端,且会占用主动消息频次 */ @Serializable + @Deprecated("Use `BodyValue`") public data class Body( @SerialName("file_type") val fileType: Int, @@ -104,4 +166,54 @@ public class UploadGroupFilesApi private constructor( @SerialName("srv_send_msg") val srvSendMsg: Boolean, ) + + + /** + * The body for [UploadGroupFilesApi]. + * + * @since 4.1.1 + */ + @Serializable + public class BodyValue { + /** + * 媒体类型:1 图片,2 视频,3 语音,4 文件(暂不开放) + * 资源格式要求: + * 图片:png/jpg,视频:mp4,语音:silk + * + * Required. + */ + @SerialName("file_type") + public var fileType: Int? = null + + /** + * 需要发送媒体资源的url + */ + public var url: String? = null + + /** + * 设置 `true` 会直接发送消息到目标端,且会占用主动消息频次 + */ + @SerialName("srv_send_msg") + public var srvSendMsg: Boolean? = null + + /** + * 上传文件的数据,如果不为 `null` 则使用 `file_data` 而不是 [url]。 + */ + @Transient + @ExperimentalQGMediaApi + public var fileDataBytes: ByteArray? = null + + @SerialName("file_data") + internal var fileDataBase64Hex: String? = null + + @OptIn(ExperimentalQGMediaApi::class) + override fun toString(): String = buildString { + append("BodyValue(fileType=").append(fileType) + append(", url=").append(url) + append(", srvSendMsg=").append(srvSendMsg) + append(", fileDataBytes=") + fileDataBytes?.joinTo(this, prefix = "[", postfix = "]", limit = 6) + append(')') + } + } } diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/api/files/UploadUserFilesApi.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/api/files/UploadUserFilesApi.kt index dad83d32..2a12b6bd 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/api/files/UploadUserFilesApi.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/api/files/UploadUserFilesApi.kt @@ -20,9 +20,13 @@ package love.forte.simbot.qguild.api.files import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import love.forte.simbot.qguild.ExperimentalQGMediaApi import love.forte.simbot.qguild.api.PostQQGuildApi import love.forte.simbot.qguild.api.SimplePostApiDescription import love.forte.simbot.qguild.model.MessageMedia +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmStatic @@ -34,7 +38,7 @@ import kotlin.jvm.JvmStatic */ public class UploadUserFilesApi private constructor( openid: String, - private val _body: Body + private val _body: BodyValue ) : PostQQGuildApi() { public companion object Factory : SimplePostApiDescription( "/v2/users/{openid}/files" @@ -49,8 +53,23 @@ public class UploadUserFilesApi private constructor( * * @param openid QQ 用户的 openid,可在各类事件中获得。 */ + @Suppress("DEPRECATION") @JvmStatic + @Deprecated("Use create(openid: String, body: BodyValue)") public fun create(openid: String, body: Body): UploadUserFilesApi = + UploadUserFilesApi(openid, BodyValue().also { + it.fileType = body.fileType + it.url = body.url + it.srvSendMsg = body.srvSendMsg + }) + + /** + * Create [UploadUserFilesApi]. + * + * @param openid QQ 用户的 openid,可在各类事件中获得。 + */ + @JvmStatic + public fun create(openid: String, body: BodyValue): UploadUserFilesApi = UploadUserFilesApi(openid, body) /** @@ -73,11 +92,41 @@ public class UploadUserFilesApi private constructor( ): UploadUserFilesApi = create( openid, - Body( - fileType = fileType, - url = url, - srvSendMsg = srvSendMsg - ) + BodyValue().also { + it.fileType = fileType + it.url = url + it.srvSendMsg = srvSendMsg + } + ) + + /** + * Create [UploadUserFilesApi]. + * + * @param openid QQ 用户的 openid,可在各类事件中获得。 + * @param fileType 媒体类型:1 图片,2 视频,3 语音,4 文件(暂不开放) + * 资源格式要求: + * 图片:png/jpg,视频:mp4,语音:silk + * @param fileData 上传文件的数据 + * @param srvSendMsg 设置 true 会直接发送消息到目标端,且会占用主动消息频次 + * + * @since 4.1.1 + */ + @ExperimentalQGMediaApi + @JvmStatic + @JvmOverloads + public fun create( + openid: String, + fileType: Int, + fileData: ByteArray, + srvSendMsg: Boolean = false + ): UploadUserFilesApi = + create( + openid, + BodyValue().also { + it.fileType = fileType + it.fileDataBytes = fileData + it.srvSendMsg = srvSendMsg + } ) } @@ -87,9 +136,26 @@ public class UploadUserFilesApi private constructor( override val path: Array = arrayOf("v2", "users", openid, "files") - override fun createBody(): Any = _body + @OptIn(ExperimentalQGMediaApi::class) + private val fileDataBytes = _body.fileDataBytes + +// override val headers: Headers = if (fileDataBytes == null) { +// Headers.Empty +// } else { +// FormDataHeader +// } + + @OptIn(ExperimentalEncodingApi::class) + override fun createBody(): Any { + if (fileDataBytes != null) { + _body.fileDataBase64Hex = Base64.encode(fileDataBytes) + } + return _body + } /** + * Deprecated: 使用非数据类的 [BodyValue] 代替此类型。 + * * @property fileType 媒体类型:1 图片,2 视频,3 语音,4 文件(暂不开放) * 资源格式要求: * 图片:png/jpg,视频:mp4,语音:silk @@ -97,6 +163,7 @@ public class UploadUserFilesApi private constructor( * @property srvSendMsg 设置 true 会直接发送消息到目标端,且会占用主动消息频次 */ @Serializable + @Deprecated("Use `BodyValue`") public data class Body( @SerialName("file_type") val fileType: Int, @@ -104,4 +171,54 @@ public class UploadUserFilesApi private constructor( @SerialName("srv_send_msg") val srvSendMsg: Boolean, ) + + /** + * The body for [UploadUserFilesApi]. + * + * @since 4.1.1 + */ + @Serializable + public class BodyValue { + /** + * 媒体类型:1 图片,2 视频,3 语音,4 文件(暂不开放) + * 资源格式要求: + * 图片:png/jpg,视频:mp4,语音:silk + * + * Required. + */ + @SerialName("file_type") + public var fileType: Int? = null + + /** + * 需要发送媒体资源的url + */ + public var url: String? = null + + /** + * 设置 `true` 会直接发送消息到目标端,且会占用主动消息频次 + */ + @SerialName("srv_send_msg") + public var srvSendMsg: Boolean? = null + + /** + * 上传文件的数据,如果不为 `null` 则使用 `file_data` 而不是 [url]。 + */ + @Transient + @ExperimentalQGMediaApi + public var fileDataBytes: ByteArray? = null + + @SerialName("file_data") + internal var fileDataBase64Hex: String? = null + + @OptIn(ExperimentalQGMediaApi::class) + override fun toString(): String = buildString { + append("BodyValue(fileType=").append(fileType) + append(", url=").append(url) + append(", srvSendMsg=").append(srvSendMsg) + append(", fileDataBytes=") + fileDataBytes?.joinTo(this, prefix = "[", postfix = "]", limit = 6) + append(')') + } + } + } diff --git a/simbot-component-qq-guild-core/api/simbot-component-qq-guild-core.api b/simbot-component-qq-guild-core/api/simbot-component-qq-guild-core.api index bf1c24cd..aef8248d 100644 --- a/simbot-component-qq-guild-core/api/simbot-component-qq-guild-core.api +++ b/simbot-component-qq-guild-core/api/simbot-component-qq-guild-core.api @@ -1586,6 +1586,7 @@ public final class love/forte/simbot/component/qguild/internal/message/QGMessage public final class love/forte/simbot/component/qguild/message/ImageParser : love/forte/simbot/component/qguild/message/SendingMessageParser { public static final field INSTANCE Llove/forte/simbot/component/qguild/message/ImageParser; + public static final fun disableBase64UploadWarn ()V public fun invoke (ILlove/forte/simbot/message/Message$Element;Llove/forte/simbot/message/Messages;Llove/forte/simbot/component/qguild/message/SendingMessageParser$BuilderContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun invoke (ILlove/forte/simbot/message/Message$Element;Llove/forte/simbot/message/Messages;Llove/forte/simbot/component/qguild/message/SendingMessageParser$GroupAndC2CBuilderContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } diff --git a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/bot/QGBot.kt b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/bot/QGBot.kt index 31068112..59c7aff9 100644 --- a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/bot/QGBot.kt +++ b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/bot/QGBot.kt @@ -42,6 +42,7 @@ import love.forte.simbot.event.Event import love.forte.simbot.message.Message import love.forte.simbot.message.MessageContent import love.forte.simbot.message.MessageReference +import love.forte.simbot.qguild.ExperimentalQGMediaApi import love.forte.simbot.qguild.QQGuildApiException import love.forte.simbot.qguild.api.MessageAuditedException import love.forte.simbot.qguild.api.QQGuildApi @@ -49,6 +50,7 @@ import love.forte.simbot.qguild.api.files.UploadGroupFilesApi import love.forte.simbot.qguild.api.files.UploadUserFilesApi import love.forte.simbot.qguild.api.message.GetMessageApi import love.forte.simbot.qguild.stdlib.* +import love.forte.simbot.resource.Resource import love.forte.simbot.suspendrunner.ST import love.forte.simbot.suspendrunner.STP import kotlin.jvm.JvmSynthetic @@ -349,6 +351,58 @@ public interface QGBot : Bot, EventMentionAware { type: Int, ): QGMedia + /** + * 上传一个资源为用于向QQ群发送的 [QGMedia], 可用于后续的发送。 + * + * @param target 目标群的ID + * @param resource 目标数据。如果是 `URIResource` 则会使用 `url`,否则通过 `file_data` 上传。 + * @param type 媒体类型。 + * + * > 1 图片,2 视频,3 语音,4 文件(暂不开放) 资源格式要求: 图片:png/ jpg,视频:mp4,语音:silk + * + * 参考 [UploadGroupFilesApi]。 + * + * @see QGGroup.uploadMedia + * + * @throws IllegalStateException 如果无法从 [resource] 中读取数据,异常会被包装在 [IllegalStateException] 中。 + * @throws Exception 任何可能在发送API时产生的异常 + * + * @since 4.1.1 + */ + @ST + @ExperimentalQGMediaApi + public suspend fun uploadGroupMedia( + target: ID, + resource: Resource, + type: Int, + ): QGMedia + + /** + * 上传一个资源为用于向QQ单聊发送的 [QGMedia], 可用于后续的发送。 + * + * @param target 目标用户的ID + * @param resource 目标数据。如果是 `URIResource` 则会使用 `url`,否则通过 `file_data` 上传。 + * @param type 媒体类型。 + * + * > 1 图片,2 视频,3 语音,4 文件(暂不开放) 资源格式要求: 图片:png/ jpg,视频:mp4,语音:silk + * + * 参考 [UploadUserFilesApi]。 + * + * @see QGFriend.uploadMedia + * + * @throws IllegalStateException 如果无法从 [resource] 中读取数据,异常会被包装在 [IllegalStateException] 中。 + * @throws Exception 任何可能在发送API时产生的异常 + * + * @since 4.1.1 + */ + @ST + @ExperimentalQGMediaApi + public suspend fun uploadUserMedia( + target: ID, + resource: Resource, + type: Int, + ): QGMedia + /** * 执行(请求) [api] 并得到原始的响应 [HttpResponse]. */ diff --git a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/friend/QGFriend.kt b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/friend/QGFriend.kt index 07944f4d..5eba6582 100644 --- a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/friend/QGFriend.kt +++ b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/friend/QGFriend.kt @@ -21,6 +21,8 @@ import love.forte.simbot.common.id.ID import love.forte.simbot.component.qguild.bot.QGBot import love.forte.simbot.component.qguild.message.QGMedia import love.forte.simbot.definition.Contact +import love.forte.simbot.qguild.ExperimentalQGMediaApi +import love.forte.simbot.resource.Resource import love.forte.simbot.suspendrunner.ST import kotlin.coroutines.CoroutineContext @@ -65,4 +67,23 @@ public interface QGFriend : Contact { url: String, type: Int, ): QGMedia + + /** + * 上传一个资源为用于向QQ单聊发送的 [QGMedia], 可用于后续的发送。 + * + * @param resource 目标数据。如果是 `URIResource` 则会使用 `url`,否则通过 `file_data` 上传。 + * @param type 媒体类型。 + * + * > 1 图片,2 视频,3 语音,4 文件(暂不开放) 资源格式要求: 图片:png/ jpg,视频:mp4,语音:silk + * + * @see QGBot.uploadUserMedia + * + * @since 4.1.1 + */ + @ST + @ExperimentalQGMediaApi + public suspend fun uploadMedia( + resource: Resource, + type: Int, + ): QGMedia } diff --git a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/group/QGGroup.kt b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/group/QGGroup.kt index 1c56d084..208d776d 100644 --- a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/group/QGGroup.kt +++ b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/group/QGGroup.kt @@ -26,6 +26,8 @@ import love.forte.simbot.component.qguild.bot.QGBot import love.forte.simbot.component.qguild.message.QGMedia import love.forte.simbot.definition.ChatGroup import love.forte.simbot.definition.Role +import love.forte.simbot.qguild.ExperimentalQGMediaApi +import love.forte.simbot.resource.Resource import love.forte.simbot.suspendrunner.ST import love.forte.simbot.suspendrunner.STP @@ -92,6 +94,27 @@ public interface QGGroup : ChatGroup { url: String, type: Int, ): QGMedia + + /** + * 上传一个资源为用于向QQ群发送的 [QGMedia], 可用于后续的发送。 + * + * 目前上传仅支持使用链接,QQ平台会对此链接进行转存。 + * + * @param resource 目标资源 + * @param type 媒体类型。 + * + * > 1 图片,2 视频,3 语音,4 文件(暂不开放) 资源格式要求: 图片:png/ jpg,视频:mp4,语音:silk + * + * @see QGBot.uploadGroupMedia + * + * @since 4.1.1 + */ + @ST + @ExperimentalQGMediaApi + public suspend fun uploadMedia( + resource: Resource, + type: Int, + ): QGMedia } /** diff --git a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/internal/bot/QGBotImpl.kt b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/internal/bot/QGBotImpl.kt index dcff66ef..49336cad 100644 --- a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/internal/bot/QGBotImpl.kt +++ b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/internal/bot/QGBotImpl.kt @@ -56,6 +56,7 @@ import love.forte.simbot.event.onEachError import love.forte.simbot.logger.LoggerFactory import love.forte.simbot.message.Message import love.forte.simbot.message.MessageContent +import love.forte.simbot.qguild.ExperimentalQGMediaApi import love.forte.simbot.qguild.QQGuildApiException import love.forte.simbot.qguild.addStackTrace import love.forte.simbot.qguild.api.channel.GetChannelApi @@ -73,6 +74,7 @@ import love.forte.simbot.qguild.model.SimpleChannel import love.forte.simbot.qguild.model.SimpleGuild import love.forte.simbot.qguild.stdlib.DisposableHandle import love.forte.simbot.qguild.stdlib.requestDataBy +import love.forte.simbot.resource.Resource import kotlin.concurrent.Volatile import kotlin.coroutines.CoroutineContext import love.forte.simbot.qguild.model.User as QGUser @@ -455,6 +457,42 @@ internal class QGBotImpl( return QGMedia(media) } + @ExperimentalQGMediaApi + override suspend fun uploadGroupMedia(target: ID, resource: Resource, type: Int): QGMedia { + val url = resource.httpUrlValue() + if (url != null) { + return uploadGroupMedia(target, url, type) + } + + val media = UploadGroupFilesApi.create( + openid = target.literal, + fileType = type, + fileData = kotlin.runCatching { resource.data() }.getOrElse { e -> + throw IllegalStateException("Failed to read data from resource $resource", e) + } + ).requestDataBy(source) + + return QGMedia(media) + } + + @ExperimentalQGMediaApi + override suspend fun uploadUserMedia(target: ID, resource: Resource, type: Int): QGMedia { + val url = resource.httpUrlValue() + if (url != null) { + return uploadUserMedia(target, url, type) + } + + val media = UploadUserFilesApi.create( + openid = target.literal, + fileType = type, + fileData = kotlin.runCatching { resource.data() }.getOrElse { e -> + throw IllegalStateException("Failed to read data from resource $resource", e) + } + ).requestDataBy(source) + + return QGMedia(media) + } + private val isTransmitCacheable = cacheable && cacheConfig?.transmitCacheConfig?.enable == true internal fun checkIfTransmitCacheable(target: T): T? = target.takeIf { isTransmitCacheable } @@ -478,3 +516,5 @@ internal inline fun QGChannel.castChannel(target: () -> return this as? T ?: throw IllegalStateException("The type of channel(id=${source.id}, name=${source.name}) is not ${target()}, it is ${source.type}") } + +internal expect fun Resource.httpUrlValue(): String? diff --git a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/internal/friend/QGFriendImpl.kt b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/internal/friend/QGFriendImpl.kt index 534527b9..560fbd69 100644 --- a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/internal/friend/QGFriendImpl.kt +++ b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/internal/friend/QGFriendImpl.kt @@ -29,8 +29,10 @@ import love.forte.simbot.component.qguild.message.sendUserMessage import love.forte.simbot.message.Message import love.forte.simbot.message.MessageContent import love.forte.simbot.message.MessageReceipt +import love.forte.simbot.qguild.ExperimentalQGMediaApi import love.forte.simbot.qguild.api.message.GroupAndC2CSendBody import love.forte.simbot.qguild.event.C2CMessageCreate +import love.forte.simbot.resource.Resource import kotlin.coroutines.CoroutineContext @@ -52,6 +54,11 @@ internal class QGFriendImpl( return bot.uploadUserMedia(id, url, type) } + @ExperimentalQGMediaApi + override suspend fun uploadMedia(resource: Resource, type: Int): QGMedia { + return bot.uploadUserMedia(id, resource, type) + } + private fun GroupAndC2CSendBody.initMsgIdAndSeq() { if (msgId == null) { if (sourceEvent != null) { diff --git a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/internal/group/QGGroupImpl.kt b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/internal/group/QGGroupImpl.kt index 2e95619a..d733815a 100644 --- a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/internal/group/QGGroupImpl.kt +++ b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/internal/group/QGGroupImpl.kt @@ -30,9 +30,11 @@ import love.forte.simbot.component.qguild.message.sendGroupMessage import love.forte.simbot.message.Message import love.forte.simbot.message.MessageContent import love.forte.simbot.message.MessageReceipt +import love.forte.simbot.qguild.ExperimentalQGMediaApi import love.forte.simbot.qguild.api.message.GroupAndC2CSendBody import love.forte.simbot.qguild.api.message.group.GroupMessageSendApi import love.forte.simbot.qguild.event.GroupAtMessageCreate +import love.forte.simbot.resource.Resource import kotlin.coroutines.CoroutineContext @@ -89,6 +91,11 @@ internal class QGGroupImpl( return bot.uploadGroupMedia(id, url, type) } + @ExperimentalQGMediaApi + override suspend fun uploadMedia(resource: Resource, type: Int): QGMedia { + return bot.uploadGroupMedia(id, resource, type) + } + override fun toString(): String { return "QGGroup(id=$id, isFake=$isFake)" } diff --git a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/message/ImageParser.kt b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/message/ImageParser.kt index 79205e88..08885a5a 100644 --- a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/message/ImageParser.kt +++ b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/message/ImageParser.kt @@ -18,11 +18,23 @@ package love.forte.simbot.component.qguild.message import io.ktor.utils.io.core.* +import love.forte.simbot.common.id.StringID.Companion.ID +import love.forte.simbot.component.qguild.message.SendingMessageParser.GroupBuilderType.C2C +import love.forte.simbot.component.qguild.message.SendingMessageParser.GroupBuilderType.GROUP import love.forte.simbot.logger.LoggerFactory import love.forte.simbot.message.* +import love.forte.simbot.qguild.ExperimentalQGMediaApi +import love.forte.simbot.qguild.api.files.UploadGroupFilesApi +import love.forte.simbot.qguild.api.files.UploadUserFilesApi import love.forte.simbot.qguild.api.message.GroupAndC2CSendBody import love.forte.simbot.resource.ByteArrayResource +import love.forte.simbot.resource.Resource +import love.forte.simbot.resource.toResource +import kotlin.concurrent.Volatile +import kotlin.jvm.JvmStatic +internal const val JVM_DISABLE_BASE64_UPLOAD_WARN = "simbot.qqguild.media.disableBase64UploadWarn" +internal expect val base64UploadWarnInitialValue: Boolean /** * @@ -31,6 +43,22 @@ import love.forte.simbot.resource.ByteArrayResource public object ImageParser : SendingMessageParser { internal val logger = LoggerFactory.getLogger("love.forte.simbot.component.qguild.message.ImageParser") + @Volatile + internal var base64UploadWarn = base64UploadWarnInitialValue + + /** + * 关闭针对 base64 上传文件的警告。 + * + * 在 JVM 中,也可以通过JVM参数 `-Dsimbot.qqguild.media.disableBase64UploadWarn=true` + * 来关闭。 + * + * @since 4.1.1 + */ + @JvmStatic + public fun disableBase64UploadWarn() { + base64UploadWarn = false + } + override suspend fun invoke( index: Int, element: Message.Element, @@ -63,7 +91,6 @@ public object ImageParser : SendingMessageParser { if (element is OfflineImage) { processOfflineImage(index, element, messages, builderContext) } - } // TODO more image type support for file_image @@ -85,6 +112,7 @@ internal fun processOfflineImage( is ByteArrayResource -> { builderContext.builderOrNew { it.fileImage == null }.setFileImage(resource.data()) } + else -> { builderContext.builderOrNew { it.fileImage == null }.setFileImage(ByteReadPacket(resource.data())) } @@ -108,7 +136,6 @@ internal suspend fun processOfflineImage( messages: Messages?, builderContext: SendingMessageParser.GroupAndC2CBuilderContext ) { - // TODO 目前只支持使用 URL 由平台转存。 processOfflineImage0(index, element, messages, builderContext) } @@ -119,6 +146,7 @@ internal suspend fun processOfflineImage( internal fun isTextOrMedia(type: Int) = when (type) { GroupAndC2CSendBody.MSG_TYPE_TEXT, GroupAndC2CSendBody.MSG_TYPE_MEDIA -> true + else -> false } @@ -128,3 +156,70 @@ internal expect suspend fun processOfflineImage0( messages: Messages?, builderContext: SendingMessageParser.GroupAndC2CBuilderContext ): Boolean + +@OptIn(ExperimentalQGMediaApi::class) +internal suspend fun processBase64OfflineImage( + index: Int, + element: OfflineImage, + resource: Resource?, + data: ByteArray, + builderContext: SendingMessageParser.GroupAndC2CBuilderContext +): Boolean { + fun builder() = builderContext.builderOrNew { + isTextOrMedia(it.msgType) && it.media == null + }.also { + it.msgType = GroupAndC2CSendBody.MSG_TYPE_MEDIA + } + + val type = builderContext.type + ImageParser.logger.debug( + "Uploading offline image via base64 to {} with target {}", + type, + builderContext.targetOpenid + ) + + if (ImageParser.base64UploadWarn) { + ImageParser.logger.warn( + "Uploading media to QQGroup or C2C using base64 is still experimental now. " + + "(for index={}, type={}, element={})" + + "The official documentation does not describe this capability and is therefore unstable. " + + "Please give preference to using `URIResource`, `OfflineURIImage` " + + "or `QGMedia` instance " + + "or see `ImageParser.disableBase64UploadWarn()` to disable this warn log.", + element::class, + index, + element, + ) + } + + val uploadedMedia = when (type) { + GROUP -> { + builderContext.bot.uploadGroupMedia( + target = builderContext.targetOpenid.ID, + resource = resource ?: data.toResource(), + type = UploadGroupFilesApi.FILE_TYPE_IMAGE, + ) + } + + C2C -> { + builderContext.bot.uploadUserMedia( + target = builderContext.targetOpenid.ID, + resource = resource ?: data.toResource(), + type = UploadUserFilesApi.FILE_TYPE_IMAGE, + ) + } + } + + ImageParser.logger.debug( + "Uploaded offline image via base64 to media {}", + uploadedMedia + ) + + val builder = builder() + builder.media = uploadedMedia.media + if (builder.content.isEmpty()) { + builder.content = " " + } + + return true +} diff --git a/simbot-component-qq-guild-core/src/jsMain/kotlin/love/forte/simbot/component/qguild/internal/bot/QGBotImpl.js.kt b/simbot-component-qq-guild-core/src/jsMain/kotlin/love/forte/simbot/component/qguild/internal/bot/QGBotImpl.js.kt new file mode 100644 index 00000000..ea42b925 --- /dev/null +++ b/simbot-component-qq-guild-core/src/jsMain/kotlin/love/forte/simbot/component/qguild/internal/bot/QGBotImpl.js.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024. ForteScarlet. + * + * This file is part of simbot-component-qq-guild. + * + * simbot-component-qq-guild is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * simbot-component-qq-guild is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with simbot-component-qq-guild. + * If not, see . + */ + +package love.forte.simbot.component.qguild.internal.bot + +import love.forte.simbot.resource.Resource + +internal actual fun Resource.httpUrlValue(): String? = null diff --git a/simbot-component-qq-guild-core/src/jsMain/kotlin/love/forte/simbot/component/qguild/message/ImageParser.js.kt b/simbot-component-qq-guild-core/src/jsMain/kotlin/love/forte/simbot/component/qguild/message/ImageParser.js.kt index f9b5f0d4..56e52f5d 100644 --- a/simbot-component-qq-guild-core/src/jsMain/kotlin/love/forte/simbot/component/qguild/message/ImageParser.js.kt +++ b/simbot-component-qq-guild-core/src/jsMain/kotlin/love/forte/simbot/component/qguild/message/ImageParser.js.kt @@ -19,6 +19,7 @@ package love.forte.simbot.component.qguild.message import love.forte.simbot.message.Messages import love.forte.simbot.message.OfflineImage +import love.forte.simbot.message.OfflineResourceImage internal actual fun processOfflineImage0( index: Int, @@ -32,4 +33,23 @@ internal actual suspend fun processOfflineImage0( element: OfflineImage, messages: Messages?, builderContext: SendingMessageParser.GroupAndC2CBuilderContext -): Boolean = false +): Boolean { + return when (element) { + is OfflineResourceImage -> processBase64OfflineImage( + index, + element, + resource = element.resource, + data = element.data(), + builderContext + ) + else -> processBase64OfflineImage( + index, + element, + resource = null, + data = element.data(), + builderContext + ) + } +} + +internal actual const val base64UploadWarnInitialValue: Boolean = true diff --git a/simbot-component-qq-guild-core/src/jvmMain/kotlin/love/forte/simbot/component/qguild/internal/bot/QGBotImpl.jvm.kt b/simbot-component-qq-guild-core/src/jvmMain/kotlin/love/forte/simbot/component/qguild/internal/bot/QGBotImpl.jvm.kt new file mode 100644 index 00000000..fdf8e216 --- /dev/null +++ b/simbot-component-qq-guild-core/src/jvmMain/kotlin/love/forte/simbot/component/qguild/internal/bot/QGBotImpl.jvm.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024. ForteScarlet. + * + * This file is part of simbot-component-qq-guild. + * + * simbot-component-qq-guild is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * simbot-component-qq-guild is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with simbot-component-qq-guild. + * If not, see . + */ + +package love.forte.simbot.component.qguild.internal.bot + +import love.forte.simbot.resource.Resource +import love.forte.simbot.resource.URIResource + +internal actual fun Resource.httpUrlValue(): String? { + if (this !is URIResource) { + return null + } + + val uri = this.uri + if (!uri.scheme.startsWith("http")) { + return null + } + + return uri.toASCIIString() +} diff --git a/simbot-component-qq-guild-core/src/jvmMain/kotlin/love/forte/simbot/component/qguild/message/ImageParser.jvm.kt b/simbot-component-qq-guild-core/src/jvmMain/kotlin/love/forte/simbot/component/qguild/message/ImageParser.jvm.kt index a1398f7b..3ad3166d 100644 --- a/simbot-component-qq-guild-core/src/jvmMain/kotlin/love/forte/simbot/component/qguild/message/ImageParser.jvm.kt +++ b/simbot-component-qq-guild-core/src/jvmMain/kotlin/love/forte/simbot/component/qguild/message/ImageParser.jvm.kt @@ -65,6 +65,7 @@ internal actual suspend fun processOfflineImage0( builderContext: SendingMessageParser.GroupAndC2CBuilderContext ): Boolean { // TODO Upload 目前只支持 URL 链接的格式 + // 2024/10/31 update: 群聊可以用 `file_data` 放 base64 数据 fun builder() = builderContext.builderOrNew { isTextOrMedia(it.msgType) && it.media == null @@ -78,20 +79,32 @@ internal actual suspend fun processOfflineImage0( if (resource is URIResource) { resource.uri.toASCIIString() } else { - ImageParser.logger.warn( - "QQGroup or C2C currently only supports sending **offline** images using URL links, " + - "the type of element {} (index={}, type={}) is not supported. " + - "Please use `URIResource`, `OfflineURIImage` or use `QGMedia` instead.", - element, + return processBase64OfflineImage( index, - element::class, + element, + resource, + runCatching { + resource.data() + }.getOrElse { e -> + throw IllegalStateException("Failed to read data from resource $resource", e) + }, + builderContext ) - - return false } } - else -> return false + else -> + return processBase64OfflineImage( + index, + element, + null, + runCatching { + element.data() + }.getOrElse { e -> + throw IllegalStateException("Failed to read data from offlineImage $element", e) + }, + builderContext + ) } val type = builderContext.type @@ -125,3 +138,7 @@ internal actual suspend fun processOfflineImage0( return true } + +internal actual val base64UploadWarnInitialValue: Boolean by lazy { + !System.getProperty(JVM_DISABLE_BASE64_UPLOAD_WARN).toBoolean() +} diff --git a/simbot-component-qq-guild-core/src/nativeMain/kotlin/love/forte/simbot/component/qguild/internal/bot/QGBotImpl.native.kt b/simbot-component-qq-guild-core/src/nativeMain/kotlin/love/forte/simbot/component/qguild/internal/bot/QGBotImpl.native.kt new file mode 100644 index 00000000..ea42b925 --- /dev/null +++ b/simbot-component-qq-guild-core/src/nativeMain/kotlin/love/forte/simbot/component/qguild/internal/bot/QGBotImpl.native.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024. ForteScarlet. + * + * This file is part of simbot-component-qq-guild. + * + * simbot-component-qq-guild is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * simbot-component-qq-guild is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with simbot-component-qq-guild. + * If not, see . + */ + +package love.forte.simbot.component.qguild.internal.bot + +import love.forte.simbot.resource.Resource + +internal actual fun Resource.httpUrlValue(): String? = null diff --git a/simbot-component-qq-guild-core/src/nativeMain/kotlin/love/forte/simbot/component/qguild/message/ImageParser.native.kt b/simbot-component-qq-guild-core/src/nativeMain/kotlin/love/forte/simbot/component/qguild/message/ImageParser.native.kt index f9b5f0d4..56e52f5d 100644 --- a/simbot-component-qq-guild-core/src/nativeMain/kotlin/love/forte/simbot/component/qguild/message/ImageParser.native.kt +++ b/simbot-component-qq-guild-core/src/nativeMain/kotlin/love/forte/simbot/component/qguild/message/ImageParser.native.kt @@ -19,6 +19,7 @@ package love.forte.simbot.component.qguild.message import love.forte.simbot.message.Messages import love.forte.simbot.message.OfflineImage +import love.forte.simbot.message.OfflineResourceImage internal actual fun processOfflineImage0( index: Int, @@ -32,4 +33,23 @@ internal actual suspend fun processOfflineImage0( element: OfflineImage, messages: Messages?, builderContext: SendingMessageParser.GroupAndC2CBuilderContext -): Boolean = false +): Boolean { + return when (element) { + is OfflineResourceImage -> processBase64OfflineImage( + index, + element, + resource = element.resource, + data = element.data(), + builderContext + ) + else -> processBase64OfflineImage( + index, + element, + resource = null, + data = element.data(), + builderContext + ) + } +} + +internal actual const val base64UploadWarnInitialValue: Boolean = true