Skip to content

Commit

Permalink
feat: support QGEmbed
Browse files Browse the repository at this point in the history
  • Loading branch information
ForteScarlet committed Nov 1, 2023
1 parent ac0bb98 commit 81ece91
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 28 deletions.
19 changes: 1 addition & 18 deletions simbot-component-qq-guild-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,6 @@

import love.forte.gradle.common.kotlin.multiplatform.NativeTargets

/*
* Copyright (c) 2023. 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 <https://www.gnu.org/licenses/>.
*/

plugins {
kotlin("multiplatform")
`qq-guild-multiplatform-maven-publish`
Expand Down Expand Up @@ -110,7 +93,7 @@ kotlin {
//// "watchosDeviceArm64",
// )

val targets = NativeTargets.Official.all.intersect(NativeTargets.Ktor.all) + setOf("mingwX64")
val targets = NativeTargets.Official.all.intersect(NativeTargets.KtorClient.all) + setOf("mingwX64")

targets {
presets.filterIsInstance<org.jetbrains.kotlin.gradle.plugin.mpp.AbstractKotlinNativeTargetPreset<*>>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ public class MessageSendApi private constructor(
public val content: String?,

/**
* 选填,embed 消息,一种特殊的 ark,详情参考[Embed消息](https://bot.q.qq.com/wiki/develop/api/openapi/message/embed_message.html)
* 选填,embed 消息,一种特殊的 ark,详情参考 [Embed消息](https://bot.q.qq.com/wiki/develop/api/openapi/message/template/embed_message.html)
*/
public val embed: Message.Embed?,
/**
Expand Down Expand Up @@ -312,6 +312,12 @@ public class MessageSendApi private constructor(
public var eventId: String? = null
public var markdown: Message.Markdown? = null

/**
* 判断 [Builder] 中的各属性是否都为空
*/
public val isEmpty: Boolean
get() = content == null && embed == null && ark == null && messageReference == null && image == null && msgId == null && eventId == null && markdown == null

public fun appendContent(append: String) {
if (content == null) {
content = append
Expand Down Expand Up @@ -399,6 +405,7 @@ public inline fun MessageSendApi.Factory.create(channelId: String, builder: Buil
* 提供一些需要由不同平台额外实现的基类。
* 主要针对 `fileImage`。
*/
@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
@InternalApi
public expect abstract class BaseMessageSendBodyBuilder() {
public open var fileImage: Any?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/

@file:Suppress("MemberVisibilityCanBePrivate")

package love.forte.simbot.qguild.message

import love.forte.simbot.qguild.model.Message
Expand Down Expand Up @@ -174,24 +175,53 @@ public class EmbedBuilder {
*/
public lateinit var prompt: String


/**
* 缩略图
*
* 选填,没有缩略图的可以不填
*/
public lateinit var thumbnail: Message.Embed.Thumbnail
public var thumbnail: Message.Embed.Thumbnail? = null

/**
* 设置缩略图。
*
* @see thumbnail
*/
public var thumbnailUrl: String?
get() = thumbnail?.url
set(value) {
thumbnail = if (value == null) {
null
} else {
Message.Embed.Thumbnail(value)
}
}

/**
* MessageEmbedField 对象数组 字段信息
*/
public var fields: MutableList<Message.Embed.Field> = mutableListOf()


/**
* 向 [fields] 中添加一个元素。
*
*/
@Deprecated("'value' is deprecated.", ReplaceWith("addField(name)"))
public fun addField(name: String, value: String) {
fields.add(Message.Embed.Field(name, value))
addField(name)
}

/**
* 向 [fields] 中添加一个元素。
*
*/
public fun addField(name: String) {
fields.add(Message.Embed.Field(name))
}

/**
* 构建得到 [Message.Embed]
*/
public fun build(): Message.Embed {
return Message.Embed(title, prompt, thumbnail, fields.toList())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import love.forte.simbot.qguild.ApiModel
import love.forte.simbot.qguild.message.EmbedBuilder
import kotlin.jvm.JvmStatic
import kotlin.jvm.JvmSynthetic

Expand Down Expand Up @@ -149,7 +150,9 @@ public data class Message(
}

/**
* [MessageEmbed](https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messageembed)
* [embed 消息](https://bot.q.qq.com/wiki/develop/api/openapi/message/template/embed_message.html)
*
* @see EmbedBuilder
*/
@Serializable
public data class Embed(
Expand All @@ -164,9 +167,9 @@ public data class Message(
val prompt: String,

/**
* 缩略图
* 缩略图,选填
*/
val thumbnail: Thumbnail,
val thumbnail: Thumbnail? = null,

/**
* 字段信息
Expand Down Expand Up @@ -202,7 +205,8 @@ public data class Message(
* _Note: 似乎已经不存在了_
*/
@Deprecated("not exists", level = DeprecationLevel.HIDDEN)
public val value: String = "",
@Transient
public val value: String? = null,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ public class QQGuildComponent : Component {
subclass(QGReplyTo.serializer())
subclass(QGContentText.serializer())
subclass(QGReference.serializer())
subclass(QGEmbed.serializer())

@Suppress("DEPRECATION")
subclass(QGAtChannel.serializer())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright (c) 2023. 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 <https://www.gnu.org/licenses/>.
*/

package love.forte.simbot.component.qguild.message

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import love.forte.simbot.event.MessageEvent
import love.forte.simbot.message.Messages
import love.forte.simbot.message.doSafeCast
import love.forte.simbot.qguild.api.message.MessageSendApi
import love.forte.simbot.qguild.message.EmbedBuilder
import love.forte.simbot.qguild.model.Message


/**
*
* embed 消息,一种特殊的 ark。
*
* [QGEmbed] 是针对 [Message.Embed] 类型的消息元素实现,在发送时会被作为一个 **独立的** [MessageSendApi] (的 `embed` 属性) 发送。
*
* 更多参考 [文档](https://bot.q.qq.com/wiki/develop/api/openapi/message/template/embed_message.html)
*
* Note: [QGEmbed] 似乎不能与 [MessageSendApi.Body.messageReference] 配合使用,
* 也就是尽可能不要在 [MessageEvent.reply] 中使用 [QGEmbed], 否则会导致此消息不可见。
*
* @see Message.Embed
* @see MessageSendApi.Body.embed
*
* @property embed 对应的 [Message.Embed] 内容。
*
* @author ForteScarlet
*/
@SerialName("qg.embed")
@Serializable
public data class QGEmbed internal constructor(public val embed: Message.Embed) : QGMessageElement<QGEmbed> {
override val key: love.forte.simbot.message.Message.Key<QGEmbed>
get() = Key

public companion object Key : love.forte.simbot.message.Message.Key<QGEmbed> {
override fun safeCast(value: Any): QGEmbed? = doSafeCast(value)

/**
* 将提供的 [Message.Embed] 包装为 [QGEmbed]。
*/
@JvmStatic
public fun byEmbed(embed: Message.Embed): QGEmbed = QGEmbed(embed)
}
}

/**
* 使用 [EmbedBuilder] 构建并得到 [QGEmbed]。
*
* @see QGEmbed
*/
public inline fun buildQGEmbed(block: EmbedBuilder.() -> Unit): QGEmbed =
QGEmbed.byEmbed(EmbedBuilder().also(block).build())


internal object EmbedParser : SendingMessageParser {
override suspend fun invoke(
index: Int,
element: love.forte.simbot.message.Message.Element<*>,
messages: Messages?,
builderContext: SendingMessageParser.BuilderContext
) {
if (element is QGEmbed) {
val embed = element.embed
// 必须是一个全空的 builder。
val builder = builderContext.builderOrNew { builder -> builder.isEmpty }
builder.embed = embed
// 下一次必须是另外新建的builder
builderContext.nextMustBeNew()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,20 +53,48 @@ public fun interface SendingMessageParser :
val builderFactory: () -> MessageSendApi.Body.Builder
) {
public val builders: Deque<MessageSendApi.Body.Builder>

/**
* 标记下一次再获取 builder 时必须新建。
*/
public var nextIsNew: Boolean = false
private set

@PublishedApi
internal fun nextMustBeNew(value: Boolean = true) {
nextIsNew = value
}

init {
builders = ConcurrentLinkedDeque()
builders.add(builderFactory())
}
val builder: MessageSendApi.Body.Builder get() = builders.peekLast()

val builder: MessageSendApi.Body.Builder get() {
if (nextIsNew) {
return newBuilder().also {
nextMustBeNew(false)
}
}

return builders.peekLast()
}
public fun newBuilder(): MessageSendApi.Body.Builder {
return builderFactory().also { builders.addLast(it) }
}

/**
* 如果符合 [check] 的条件,得到 [builder], 否则使用 [newBuilder] 构建一个新的builder。
*
* 如果 [nextIsNew] 被标记为 `true`, 则本次不会调用 [check] 且必然得到新的 builder.
*
*/
public inline fun builderOrNew(check: (MessageSendApi.Body.Builder) -> Boolean): MessageSendApi.Body.Builder {
if (nextIsNew) {
return newBuilder().also {
nextMustBeNew(false)
}
}
return builder.takeIf(check) ?: newBuilder()
}

Expand Down Expand Up @@ -101,6 +129,7 @@ public object MessageParsers {
add(FaceParser)
add(MentionParser)
add(ArkParser)
add(EmbedParser)
add(AttachmentParser)
add(ReplyToParser)
add(ImageParser)
Expand Down

0 comments on commit 81ece91

Please sign in to comment.