mirror of
https://github.com/InsanusMokrassar/TelegramBotAPI.git
synced 2026-06-20 02:06:11 +00:00
Add Bot API 10.1 Rich Messages support
Adds the full Rich Messages type system and methods: - RichText hierarchy: RichTextPlain, RichTextGroup, RichTextEntity and all 24 RichText* entity types, with a recursive serializer handling plain strings, arrays and typed objects - RichBlock hierarchy: all 21 RichBlock* types plus RichBlockCaption, RichBlockTableCell and RichBlockListItem (JsonContentPolymorphic by type) - RichMessage type and RichMessageContent message content; rich_message parsed in RawMessage; RichMessageContentMessage typealias - InputRichMessage (internal constructor + InputRichMessageHTML / InputRichMessageMarkdown factories) and InputRichMessageContent usable as InputMessageContent - SendRichMessage and SendRichMessageDraft requests with API bindings - richMessage parameter on EditChatMessageText - Serialization round-trip test for RichMessage/RichText/RichBlock Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,7 @@ import dev.inmo.tgbotapi.types.message.RawMessageEntity
|
||||
import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage
|
||||
import dev.inmo.tgbotapi.types.message.content.TextContent
|
||||
import dev.inmo.tgbotapi.types.message.toRawMessageEntities
|
||||
import dev.inmo.tgbotapi.types.rich.InputRichMessage
|
||||
import dev.inmo.tgbotapi.utils.extensions.makeString
|
||||
import kotlinx.serialization.*
|
||||
|
||||
@@ -73,7 +74,9 @@ data class EditChatMessageText internal constructor(
|
||||
@SerialName(linkPreviewOptionsField)
|
||||
override val linkPreviewOptions: LinkPreviewOptions? = null,
|
||||
@SerialName(replyMarkupField)
|
||||
override val replyMarkup: InlineKeyboardMarkup? = null
|
||||
override val replyMarkup: InlineKeyboardMarkup? = null,
|
||||
@SerialName(richMessageField)
|
||||
val richMessage: InputRichMessage? = null
|
||||
) : EditChatMessage<TextContent>, EditTextChatMessage, EditReplyMessage, EditLinkPreviewOptionsContainer {
|
||||
override val textSources: TextSourcesList? by lazy {
|
||||
rawEntities ?.asTextSources(text)
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package dev.inmo.tgbotapi.requests.send
|
||||
|
||||
import dev.inmo.tgbotapi.requests.send.abstracts.ReplyingMarkupSendMessageRequest
|
||||
import dev.inmo.tgbotapi.requests.send.abstracts.SendContentMessageRequest
|
||||
import dev.inmo.tgbotapi.types.*
|
||||
import dev.inmo.tgbotapi.types.business_connection.BusinessConnectionId
|
||||
import dev.inmo.tgbotapi.types.buttons.KeyboardMarkup
|
||||
import dev.inmo.tgbotapi.types.message.*
|
||||
import dev.inmo.tgbotapi.types.message.abstracts.ChatContentMessage
|
||||
import dev.inmo.tgbotapi.types.message.abstracts.TelegramBotAPIMessageDeserializationStrategyClass
|
||||
import dev.inmo.tgbotapi.types.message.content.RichMessageContent
|
||||
import dev.inmo.tgbotapi.types.rich.InputRichMessage
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.EncodeDefault
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.SerializationStrategy
|
||||
|
||||
internal val RichMessageContentMessageResultDeserializer: DeserializationStrategy<ChatContentMessage<RichMessageContent>>
|
||||
= TelegramBotAPIMessageDeserializationStrategyClass()
|
||||
|
||||
/**
|
||||
* Use this method to send rich messages.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#sendrichmessage">sendRichMessage</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class SendRichMessage(
|
||||
@SerialName(chatIdField)
|
||||
override val chatId: ChatIdentifier,
|
||||
@SerialName(richMessageField)
|
||||
val richMessage: InputRichMessage,
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
@SerialName(messageThreadIdField)
|
||||
@EncodeDefault
|
||||
override val threadId: MessageThreadId? = chatId.threadId,
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
@EncodeDefault
|
||||
@SerialName(directMessagesTopicIdField)
|
||||
override val directMessageThreadId: DirectMessageThreadId? = chatId.directMessageThreadId,
|
||||
@SerialName(businessConnectionIdField)
|
||||
override val businessConnectionId: BusinessConnectionId? = chatId.businessConnectionId,
|
||||
@SerialName(disableNotificationField)
|
||||
override val disableNotification: Boolean = false,
|
||||
@SerialName(protectContentField)
|
||||
override val protectContent: Boolean = false,
|
||||
@SerialName(allowPaidBroadcastField)
|
||||
override val allowPaidBroadcast: Boolean = false,
|
||||
@SerialName(messageEffectIdField)
|
||||
override val effectId: EffectId? = null,
|
||||
@SerialName(suggestedPostParametersField)
|
||||
override val suggestedPostParameters: SuggestedPostParameters? = null,
|
||||
@SerialName(replyParametersField)
|
||||
override val replyParameters: ReplyParameters? = null,
|
||||
@SerialName(replyMarkupField)
|
||||
override val replyMarkup: KeyboardMarkup? = null
|
||||
) : SendContentMessageRequest<ChatContentMessage<RichMessageContent>>,
|
||||
ReplyingMarkupSendMessageRequest<ChatContentMessage<RichMessageContent>> {
|
||||
override fun method(): String = "sendRichMessage"
|
||||
override val resultDeserializer: DeserializationStrategy<ChatContentMessage<RichMessageContent>>
|
||||
get() = RichMessageContentMessageResultDeserializer
|
||||
override val requestSerializer: SerializationStrategy<*>
|
||||
get() = serializer()
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package dev.inmo.tgbotapi.requests.send
|
||||
|
||||
import dev.inmo.tgbotapi.requests.abstracts.SimpleRequest
|
||||
import dev.inmo.tgbotapi.types.ChatId
|
||||
import dev.inmo.tgbotapi.types.MessageThreadId
|
||||
import dev.inmo.tgbotapi.types.chatIdField
|
||||
import dev.inmo.tgbotapi.types.draftIdField
|
||||
import dev.inmo.tgbotapi.types.messageThreadIdField
|
||||
import dev.inmo.tgbotapi.types.richMessageField
|
||||
import dev.inmo.tgbotapi.types.rich.InputRichMessage
|
||||
import dev.inmo.tgbotapi.utils.serializers.UnitFromBooleanSerializer
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.SerializationStrategy
|
||||
|
||||
/**
|
||||
* Use this method to stream a partial rich message to a user while the message is being generated. The streamed draft is
|
||||
* ephemeral and acts as a temporary 30-second preview - once the output is finalized, [SendRichMessage] must be called
|
||||
* with the complete message to persist it in the user's chat.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#sendrichmessagedraft">sendRichMessageDraft</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class SendRichMessageDraft(
|
||||
@SerialName(chatIdField)
|
||||
val chatId: ChatId,
|
||||
/**
|
||||
* Unique identifier of the message draft; must be non-zero. Changes to drafts with the same identifier are animated.
|
||||
*/
|
||||
@SerialName(draftIdField)
|
||||
val draftId: Long,
|
||||
@SerialName(richMessageField)
|
||||
val richMessage: InputRichMessage,
|
||||
@SerialName(messageThreadIdField)
|
||||
val threadId: MessageThreadId? = null
|
||||
) : SimpleRequest<Unit> {
|
||||
init {
|
||||
require(draftId != 0L) {
|
||||
"draftId of SendRichMessageDraft must be non-zero"
|
||||
}
|
||||
}
|
||||
|
||||
override val requestSerializer: SerializationStrategy<*>
|
||||
get() = serializer()
|
||||
|
||||
override val resultDeserializer: DeserializationStrategy<Unit>
|
||||
get() = UnitFromBooleanSerializer
|
||||
|
||||
override fun method(): String = "sendRichMessageDraft"
|
||||
}
|
||||
@@ -573,6 +573,40 @@ const val supportsJoinRequestQueriesField = "supports_join_request_queries"
|
||||
const val queryIdField = "query_id"
|
||||
const val chatJoinRequestQueryIdField = "chat_join_request_query_id"
|
||||
const val webAppUrlField = "web_app_url"
|
||||
const val richMessageField = "rich_message"
|
||||
const val isRtlField = "is_rtl"
|
||||
const val skipEntityDetectionField = "skip_entity_detection"
|
||||
const val markdownField = "markdown"
|
||||
const val htmlField = "html"
|
||||
const val unixTimeField = "unix_time"
|
||||
const val dateTimeFormatField = "date_time_format"
|
||||
const val alternativeTextField = "alternative_text"
|
||||
const val expressionField = "expression"
|
||||
const val emailAddressField = "email_address"
|
||||
const val hashtagField = "hashtag"
|
||||
const val cashtagField = "cashtag"
|
||||
const val bankCardNumberField = "bank_card_number"
|
||||
const val anchorNameField = "anchor_name"
|
||||
const val referenceNameField = "reference_name"
|
||||
const val blocksField = "blocks"
|
||||
const val itemsField = "items"
|
||||
const val summaryField = "summary"
|
||||
const val sizeField = "size"
|
||||
const val languageField = "language"
|
||||
const val creditField = "credit"
|
||||
const val cellsField = "cells"
|
||||
const val isHeaderField = "is_header"
|
||||
const val colspanField = "colspan"
|
||||
const val rowspanField = "rowspan"
|
||||
const val alignField = "align"
|
||||
const val valignField = "valign"
|
||||
const val zoomField = "zoom"
|
||||
const val voiceNoteField = "voice_note"
|
||||
const val hasCheckboxField = "has_checkbox"
|
||||
const val isCheckedField = "is_checked"
|
||||
const val isOpenField = "is_open"
|
||||
const val isBorderedField = "is_bordered"
|
||||
const val isStripedField = "is_striped"
|
||||
const val certificateField = "certificate"
|
||||
const val questionField = "question"
|
||||
const val questionEntitiesField = "question_entities"
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package dev.inmo.tgbotapi.types.InlineQueries.InputMessageContent
|
||||
|
||||
import dev.inmo.tgbotapi.types.richMessageField
|
||||
import dev.inmo.tgbotapi.types.rich.InputRichMessage
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents the content of a rich message to be sent as the result of an inline query.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#inputrichmessagecontent">InputRichMessageContent</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class InputRichMessageContent(
|
||||
@SerialName(richMessageField)
|
||||
val richMessage: InputRichMessage
|
||||
) : InputMessageContent
|
||||
@@ -22,6 +22,7 @@ object InputMessageContentSerializer : KSerializer<InputMessageContent> {
|
||||
is InputTextMessageContent -> InputTextMessageContent.serializer().serialize(encoder, value)
|
||||
is InputVenueMessageContent -> InputVenueMessageContent.serializer().serialize(encoder, value)
|
||||
is InputInvoiceMessageContent -> InputInvoiceMessageContent.serializer().serialize(encoder, value)
|
||||
is InputRichMessageContent -> InputRichMessageContent.serializer().serialize(encoder, value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ import dev.inmo.tgbotapi.types.polls.Poll
|
||||
import dev.inmo.tgbotapi.types.polls.PollOptionPersistentId
|
||||
import dev.inmo.tgbotapi.types.request.ChatShared
|
||||
import dev.inmo.tgbotapi.types.request.UsersShared
|
||||
import dev.inmo.tgbotapi.types.rich.RichMessage
|
||||
import dev.inmo.tgbotapi.types.stories.Story
|
||||
import dev.inmo.tgbotapi.types.venue.Venue
|
||||
import dev.inmo.tgbotapi.utils.isFakeTelegramUser
|
||||
@@ -92,6 +93,7 @@ internal data class RawMessage(
|
||||
private val caption_entities: RawMessageEntities? = null,
|
||||
private val has_media_spoiler: Boolean? = null,
|
||||
private val story: Story? = null,
|
||||
private val rich_message: RichMessage? = null,
|
||||
private val audio: AudioFile? = null,
|
||||
private val document: DocumentFile? = null,
|
||||
private val paid_media: PaidMediaInfo? = null,
|
||||
@@ -230,6 +232,11 @@ internal data class RawMessage(
|
||||
} ?: emptyList()
|
||||
|
||||
when {
|
||||
rich_message != null -> RichMessageContent(
|
||||
chat,
|
||||
messageId,
|
||||
rich_message
|
||||
)
|
||||
story != null -> StoryContent(
|
||||
chat,
|
||||
messageId,
|
||||
|
||||
@@ -55,6 +55,7 @@ sealed interface MessageContent: ResendableContent {
|
||||
subclass(StoryContent::class)
|
||||
subclass(GiveawayPublicResultsContent::class)
|
||||
subclass(GiveawayContent::class)
|
||||
subclass(RichMessageContent::class)
|
||||
|
||||
additionalBuilder()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package dev.inmo.tgbotapi.types.message.content
|
||||
|
||||
import dev.inmo.tgbotapi.requests.ForwardMessage
|
||||
import dev.inmo.tgbotapi.requests.abstracts.Request
|
||||
import dev.inmo.tgbotapi.types.*
|
||||
import dev.inmo.tgbotapi.types.business_connection.BusinessConnectionId
|
||||
import dev.inmo.tgbotapi.types.buttons.KeyboardMarkup
|
||||
import dev.inmo.tgbotapi.types.chat.Chat
|
||||
import dev.inmo.tgbotapi.types.message.SuggestedPostParameters
|
||||
import dev.inmo.tgbotapi.types.message.abstracts.ChatContentMessage
|
||||
import dev.inmo.tgbotapi.types.rich.RichMessage
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class RichMessageContent(
|
||||
private val chat: Chat,
|
||||
private val messageId: MessageId,
|
||||
val richMessage: RichMessage
|
||||
) : MessageContent {
|
||||
override fun createResend(
|
||||
chatId: ChatIdentifier,
|
||||
messageThreadId: MessageThreadId?,
|
||||
directMessageThreadId: DirectMessageThreadId?,
|
||||
businessConnectionId: BusinessConnectionId?,
|
||||
disableNotification: Boolean,
|
||||
protectContent: Boolean,
|
||||
allowPaidBroadcast: Boolean,
|
||||
effectId: EffectId?,
|
||||
suggestedPostParameters: SuggestedPostParameters?,
|
||||
replyParameters: ReplyParameters?,
|
||||
replyMarkup: KeyboardMarkup?
|
||||
): Request<ChatContentMessage<RichMessageContent>> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return ForwardMessage(
|
||||
chat.id,
|
||||
toChatId = chatId,
|
||||
messageId = messageId,
|
||||
threadId = messageThreadId,
|
||||
directMessageThreadId = directMessageThreadId,
|
||||
disableNotification = disableNotification,
|
||||
protectContent = protectContent
|
||||
) as Request<ChatContentMessage<RichMessageContent>>
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,8 @@ typealias PollMessage = ChatContentMessage<PollContent>
|
||||
typealias TextMessage = ChatContentMessage<TextContent>
|
||||
typealias StoryMessage = ChatContentMessage<StoryContent>
|
||||
|
||||
typealias RichMessageContentMessage = ChatContentMessage<RichMessageContent>
|
||||
|
||||
typealias LocationMessage = ChatContentMessage<LocationContent>
|
||||
typealias LiveLocationMessage = ChatContentMessage<LiveLocationContent>
|
||||
typealias StaticLocationMessage = ChatContentMessage<StaticLocationContent>
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package dev.inmo.tgbotapi.types.rich
|
||||
|
||||
import dev.inmo.tgbotapi.types.htmlField
|
||||
import dev.inmo.tgbotapi.types.isRtlField
|
||||
import dev.inmo.tgbotapi.types.markdownField
|
||||
import dev.inmo.tgbotapi.types.skipEntityDetectionField
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Describes a rich message to be sent. Exactly one of the fields [html] or [markdown] must be used. Use the
|
||||
* [InputRichMessageHTML] and [InputRichMessageMarkdown] factories to build an instance.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#inputrichmessage">InputRichMessage</a>
|
||||
*/
|
||||
@ConsistentCopyVisibility
|
||||
@Serializable
|
||||
data class InputRichMessage internal constructor(
|
||||
@SerialName(htmlField)
|
||||
val html: String? = null,
|
||||
@SerialName(markdownField)
|
||||
val markdown: String? = null,
|
||||
@SerialName(isRtlField)
|
||||
val isRtl: Boolean? = null,
|
||||
@SerialName(skipEntityDetectionField)
|
||||
val skipEntityDetection: Boolean? = null
|
||||
) {
|
||||
init {
|
||||
require((html == null) != (markdown == null)) {
|
||||
"Exactly one of the fields html or markdown must be used in InputRichMessage"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an [InputRichMessage] with the content described using HTML formatting.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#inputrichmessage">InputRichMessage</a>
|
||||
*/
|
||||
fun InputRichMessageHTML(
|
||||
html: String,
|
||||
isRtl: Boolean? = null,
|
||||
skipEntityDetection: Boolean? = null
|
||||
): InputRichMessage = InputRichMessage(
|
||||
html = html,
|
||||
markdown = null,
|
||||
isRtl = isRtl,
|
||||
skipEntityDetection = skipEntityDetection
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates an [InputRichMessage] with the content described using Markdown formatting.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#inputrichmessage">InputRichMessage</a>
|
||||
*/
|
||||
fun InputRichMessageMarkdown(
|
||||
markdown: String,
|
||||
isRtl: Boolean? = null,
|
||||
skipEntityDetection: Boolean? = null
|
||||
): InputRichMessage = InputRichMessage(
|
||||
html = null,
|
||||
markdown = markdown,
|
||||
isRtl = isRtl,
|
||||
skipEntityDetection = skipEntityDetection
|
||||
)
|
||||
@@ -0,0 +1,50 @@
|
||||
package dev.inmo.tgbotapi.types.rich
|
||||
|
||||
import dev.inmo.tgbotapi.types.typeField
|
||||
import dev.inmo.tgbotapi.utils.internal.ClassCastsIncluded
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
|
||||
/**
|
||||
* Represents a block in a rich formatted message.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richblock">RichBlock</a>
|
||||
*/
|
||||
@Serializable(RichBlockSerializer::class)
|
||||
@ClassCastsIncluded
|
||||
sealed interface RichBlock {
|
||||
val type: String
|
||||
}
|
||||
|
||||
object RichBlockSerializer : JsonContentPolymorphicSerializer<RichBlock>(RichBlock::class) {
|
||||
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<RichBlock> {
|
||||
return when (val type = element.jsonObject[typeField]?.jsonPrimitive?.content) {
|
||||
RichBlockParagraph.TYPE -> RichBlockParagraph.serializer()
|
||||
RichBlockSectionHeading.TYPE -> RichBlockSectionHeading.serializer()
|
||||
RichBlockPreformatted.TYPE -> RichBlockPreformatted.serializer()
|
||||
RichBlockFooter.TYPE -> RichBlockFooter.serializer()
|
||||
RichBlockDivider.TYPE -> RichBlockDivider.serializer()
|
||||
RichBlockMathematicalExpression.TYPE -> RichBlockMathematicalExpression.serializer()
|
||||
RichBlockAnchor.TYPE -> RichBlockAnchor.serializer()
|
||||
RichBlockList.TYPE -> RichBlockList.serializer()
|
||||
RichBlockBlockQuotation.TYPE -> RichBlockBlockQuotation.serializer()
|
||||
RichBlockPullQuotation.TYPE -> RichBlockPullQuotation.serializer()
|
||||
RichBlockCollage.TYPE -> RichBlockCollage.serializer()
|
||||
RichBlockSlideshow.TYPE -> RichBlockSlideshow.serializer()
|
||||
RichBlockTable.TYPE -> RichBlockTable.serializer()
|
||||
RichBlockDetails.TYPE -> RichBlockDetails.serializer()
|
||||
RichBlockMap.TYPE -> RichBlockMap.serializer()
|
||||
RichBlockAnimation.TYPE -> RichBlockAnimation.serializer()
|
||||
RichBlockAudio.TYPE -> RichBlockAudio.serializer()
|
||||
RichBlockPhoto.TYPE -> RichBlockPhoto.serializer()
|
||||
RichBlockVideo.TYPE -> RichBlockVideo.serializer()
|
||||
RichBlockVoiceNote.TYPE -> RichBlockVoiceNote.serializer()
|
||||
RichBlockThinking.TYPE -> RichBlockThinking.serializer()
|
||||
else -> error("Unknown RichBlock type: $type")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package dev.inmo.tgbotapi.types.rich
|
||||
|
||||
import dev.inmo.tgbotapi.types.alignField
|
||||
import dev.inmo.tgbotapi.types.blocksField
|
||||
import dev.inmo.tgbotapi.types.colspanField
|
||||
import dev.inmo.tgbotapi.types.creditField
|
||||
import dev.inmo.tgbotapi.types.hasCheckboxField
|
||||
import dev.inmo.tgbotapi.types.isCheckedField
|
||||
import dev.inmo.tgbotapi.types.isHeaderField
|
||||
import dev.inmo.tgbotapi.types.labelField
|
||||
import dev.inmo.tgbotapi.types.rowspanField
|
||||
import dev.inmo.tgbotapi.types.textField
|
||||
import dev.inmo.tgbotapi.types.typeField
|
||||
import dev.inmo.tgbotapi.types.valignField
|
||||
import dev.inmo.tgbotapi.types.valueField
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Caption of a rich formatted block.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richblockcaption">RichBlockCaption</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichBlockCaption(
|
||||
@SerialName(textField)
|
||||
val text: RichText,
|
||||
@SerialName(creditField)
|
||||
val credit: RichText? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* A cell in a [RichBlockTable].
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richblocktablecell">RichBlockTableCell</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichBlockTableCell(
|
||||
@SerialName(textField)
|
||||
val text: RichText? = null,
|
||||
@SerialName(isHeaderField)
|
||||
val isHeader: Boolean? = null,
|
||||
@SerialName(colspanField)
|
||||
val colspan: Int? = null,
|
||||
@SerialName(rowspanField)
|
||||
val rowspan: Int? = null,
|
||||
@SerialName(alignField)
|
||||
val align: String,
|
||||
@SerialName(valignField)
|
||||
val valign: String
|
||||
)
|
||||
|
||||
/**
|
||||
* An item of a [RichBlockList].
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richblocklistitem">RichBlockListItem</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichBlockListItem(
|
||||
@SerialName(labelField)
|
||||
val label: String,
|
||||
@SerialName(blocksField)
|
||||
val blocks: List<RichBlock>,
|
||||
@SerialName(hasCheckboxField)
|
||||
val hasCheckbox: Boolean? = null,
|
||||
@SerialName(isCheckedField)
|
||||
val isChecked: Boolean? = null,
|
||||
@SerialName(valueField)
|
||||
val value: Int? = null,
|
||||
/**
|
||||
* For ordered lists, the type of the item label; must be one of "a", "A", "i", "I" or "1".
|
||||
*/
|
||||
@SerialName(typeField)
|
||||
val labelType: String? = null
|
||||
)
|
||||
@@ -0,0 +1,490 @@
|
||||
package dev.inmo.tgbotapi.types.rich
|
||||
|
||||
import dev.inmo.tgbotapi.types.animationField
|
||||
import dev.inmo.tgbotapi.types.audioField
|
||||
import dev.inmo.tgbotapi.types.blocksField
|
||||
import dev.inmo.tgbotapi.types.captionField
|
||||
import dev.inmo.tgbotapi.types.cellsField
|
||||
import dev.inmo.tgbotapi.types.creditField
|
||||
import dev.inmo.tgbotapi.types.expressionField
|
||||
import dev.inmo.tgbotapi.types.files.AnimationFile
|
||||
import dev.inmo.tgbotapi.types.files.AudioFile
|
||||
import dev.inmo.tgbotapi.types.files.PhotoFile
|
||||
import dev.inmo.tgbotapi.types.files.VideoFile
|
||||
import dev.inmo.tgbotapi.types.files.VoiceFile
|
||||
import dev.inmo.tgbotapi.types.hasSpoilerField
|
||||
import dev.inmo.tgbotapi.types.heightField
|
||||
import dev.inmo.tgbotapi.types.isBorderedField
|
||||
import dev.inmo.tgbotapi.types.isOpenField
|
||||
import dev.inmo.tgbotapi.types.isStripedField
|
||||
import dev.inmo.tgbotapi.types.itemsField
|
||||
import dev.inmo.tgbotapi.types.languageField
|
||||
import dev.inmo.tgbotapi.types.location.StaticLocation
|
||||
import dev.inmo.tgbotapi.types.locationField
|
||||
import dev.inmo.tgbotapi.types.nameField
|
||||
import dev.inmo.tgbotapi.types.photoField
|
||||
import dev.inmo.tgbotapi.types.sizeField
|
||||
import dev.inmo.tgbotapi.types.summaryField
|
||||
import dev.inmo.tgbotapi.types.textField
|
||||
import dev.inmo.tgbotapi.types.typeField
|
||||
import dev.inmo.tgbotapi.types.videoField
|
||||
import dev.inmo.tgbotapi.types.voiceNoteField
|
||||
import dev.inmo.tgbotapi.types.widthField
|
||||
import dev.inmo.tgbotapi.types.zoomField
|
||||
import kotlinx.serialization.EncodeDefault
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* A text paragraph.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richblockparagraph">RichBlockParagraph</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichBlockParagraph(
|
||||
@SerialName(textField)
|
||||
val text: RichText
|
||||
) : RichBlock {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "paragraph"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A section heading.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richblocksectionheading">RichBlockSectionHeading</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichBlockSectionHeading(
|
||||
@SerialName(textField)
|
||||
val text: RichText,
|
||||
/**
|
||||
* Relative size of the text font; 1-6, 1 is the largest, 6 is the smallest.
|
||||
*/
|
||||
@SerialName(sizeField)
|
||||
val size: Int
|
||||
) : RichBlock {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "heading"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A preformatted text block.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richblockpreformatted">RichBlockPreformatted</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichBlockPreformatted(
|
||||
@SerialName(textField)
|
||||
val text: RichText,
|
||||
@SerialName(languageField)
|
||||
val language: String? = null
|
||||
) : RichBlock {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "pre"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A footer.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richblockfooter">RichBlockFooter</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichBlockFooter(
|
||||
@SerialName(textField)
|
||||
val text: RichText
|
||||
) : RichBlock {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "footer"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A divider.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richblockdivider">RichBlockDivider</a>
|
||||
*/
|
||||
@Serializable
|
||||
class RichBlockDivider : RichBlock {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
override fun equals(other: Any?): Boolean = other is RichBlockDivider
|
||||
|
||||
override fun hashCode(): Int = TYPE.hashCode()
|
||||
|
||||
override fun toString(): String = "RichBlockDivider"
|
||||
|
||||
companion object {
|
||||
const val TYPE = "divider"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A block with a mathematical expression in LaTeX format.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richblockmathematicalexpression">RichBlockMathematicalExpression</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichBlockMathematicalExpression(
|
||||
@SerialName(expressionField)
|
||||
val expression: String
|
||||
) : RichBlock {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "mathematical_expression"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A block with an anchor.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richblockanchor">RichBlockAnchor</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichBlockAnchor(
|
||||
@SerialName(nameField)
|
||||
val name: String
|
||||
) : RichBlock {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "anchor"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of blocks.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richblocklist">RichBlockList</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichBlockList(
|
||||
@SerialName(itemsField)
|
||||
val items: List<RichBlockListItem>
|
||||
) : RichBlock {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "list"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A block quotation.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richblockblockquotation">RichBlockBlockQuotation</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichBlockBlockQuotation(
|
||||
@SerialName(blocksField)
|
||||
val blocks: List<RichBlock>,
|
||||
@SerialName(creditField)
|
||||
val credit: RichText? = null
|
||||
) : RichBlock {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "blockquote"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A quotation with centered text.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richblockpullquotation">RichBlockPullQuotation</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichBlockPullQuotation(
|
||||
@SerialName(textField)
|
||||
val text: RichText,
|
||||
@SerialName(creditField)
|
||||
val credit: RichText? = null
|
||||
) : RichBlock {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "pullquote"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A collage.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richblockcollage">RichBlockCollage</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichBlockCollage(
|
||||
@SerialName(blocksField)
|
||||
val blocks: List<RichBlock>,
|
||||
@SerialName(captionField)
|
||||
val caption: RichBlockCaption? = null
|
||||
) : RichBlock {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "collage"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A slideshow.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richblockslideshow">RichBlockSlideshow</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichBlockSlideshow(
|
||||
@SerialName(blocksField)
|
||||
val blocks: List<RichBlock>,
|
||||
@SerialName(captionField)
|
||||
val caption: RichBlockCaption? = null
|
||||
) : RichBlock {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "slideshow"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A table.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richblocktable">RichBlockTable</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichBlockTable(
|
||||
@SerialName(cellsField)
|
||||
val cells: List<List<RichBlockTableCell>>,
|
||||
@SerialName(isBorderedField)
|
||||
val isBordered: Boolean? = null,
|
||||
@SerialName(isStripedField)
|
||||
val isStriped: Boolean? = null,
|
||||
@SerialName(captionField)
|
||||
val caption: RichText? = null
|
||||
) : RichBlock {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "table"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An expandable block for details disclosure.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richblockdetails">RichBlockDetails</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichBlockDetails(
|
||||
@SerialName(summaryField)
|
||||
val summary: RichText,
|
||||
@SerialName(blocksField)
|
||||
val blocks: List<RichBlock>,
|
||||
@SerialName(isOpenField)
|
||||
val isOpen: Boolean? = null
|
||||
) : RichBlock {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "details"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A block with a map.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richblockmap">RichBlockMap</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichBlockMap(
|
||||
@SerialName(locationField)
|
||||
val location: StaticLocation,
|
||||
/**
|
||||
* Map zoom level; 13-20.
|
||||
*/
|
||||
@SerialName(zoomField)
|
||||
val zoom: Int,
|
||||
@SerialName(widthField)
|
||||
val width: Int,
|
||||
@SerialName(heightField)
|
||||
val height: Int,
|
||||
@SerialName(captionField)
|
||||
val caption: RichBlockCaption? = null
|
||||
) : RichBlock {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "map"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A block with an animation.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richblockanimation">RichBlockAnimation</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichBlockAnimation(
|
||||
@SerialName(animationField)
|
||||
val animation: AnimationFile,
|
||||
@SerialName(hasSpoilerField)
|
||||
val hasSpoiler: Boolean? = null,
|
||||
@SerialName(captionField)
|
||||
val caption: RichBlockCaption? = null
|
||||
) : RichBlock {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "animation"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A block with a music file.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richblockaudio">RichBlockAudio</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichBlockAudio(
|
||||
@SerialName(audioField)
|
||||
val audio: AudioFile,
|
||||
@SerialName(captionField)
|
||||
val caption: RichBlockCaption? = null
|
||||
) : RichBlock {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "audio"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A block with a photo.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richblockphoto">RichBlockPhoto</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichBlockPhoto(
|
||||
@SerialName(photoField)
|
||||
val photo: PhotoFile,
|
||||
@SerialName(hasSpoilerField)
|
||||
val hasSpoiler: Boolean? = null,
|
||||
@SerialName(captionField)
|
||||
val caption: RichBlockCaption? = null
|
||||
) : RichBlock {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "photo"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A block with a video.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richblockvideo">RichBlockVideo</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichBlockVideo(
|
||||
@SerialName(videoField)
|
||||
val video: VideoFile,
|
||||
@SerialName(hasSpoilerField)
|
||||
val hasSpoiler: Boolean? = null,
|
||||
@SerialName(captionField)
|
||||
val caption: RichBlockCaption? = null
|
||||
) : RichBlock {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "video"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A block with a voice note.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richblockvoicenote">RichBlockVoiceNote</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichBlockVoiceNote(
|
||||
@SerialName(voiceNoteField)
|
||||
val voiceNote: VoiceFile,
|
||||
@SerialName(captionField)
|
||||
val caption: RichBlockCaption? = null
|
||||
) : RichBlock {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "voice_note"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A block with a "Thinking…" placeholder. May be used only in sendRichMessageDraft.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richblockthinking">RichBlockThinking</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichBlockThinking(
|
||||
@SerialName(textField)
|
||||
val text: RichText
|
||||
) : RichBlock {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "thinking"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package dev.inmo.tgbotapi.types.rich
|
||||
|
||||
import dev.inmo.tgbotapi.types.blocksField
|
||||
import dev.inmo.tgbotapi.types.isRtlField
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Rich formatted message.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richmessage">RichMessage</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichMessage(
|
||||
@SerialName(blocksField)
|
||||
val blocks: List<RichBlock>,
|
||||
@SerialName(isRtlField)
|
||||
val isRtl: Boolean? = null
|
||||
)
|
||||
@@ -0,0 +1,104 @@
|
||||
package dev.inmo.tgbotapi.types.rich
|
||||
|
||||
import dev.inmo.tgbotapi.types.typeField
|
||||
import dev.inmo.tgbotapi.utils.internal.ClassCastsIncluded
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.*
|
||||
|
||||
/**
|
||||
* Represents a rich formatted text. It can be either a plain text ([RichTextPlain]), a group of rich texts
|
||||
* ([RichTextGroup]) or any of [RichTextEntity] subtypes.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtext">RichText</a>
|
||||
*/
|
||||
@Serializable(RichTextSerializer::class)
|
||||
@ClassCastsIncluded
|
||||
sealed interface RichText
|
||||
|
||||
/**
|
||||
* A plain (non-formatted) part of a [RichText]. Serialized as a bare JSON string.
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextPlain(
|
||||
val text: String
|
||||
) : RichText
|
||||
|
||||
/**
|
||||
* A group of [RichText]s. Serialized as a JSON array.
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextGroup(
|
||||
val parts: List<RichText>
|
||||
) : RichText
|
||||
|
||||
/**
|
||||
* Any typed (formatted) part of a [RichText]. Serialized as a JSON object with the [type] discriminator.
|
||||
*/
|
||||
@Serializable(RichTextEntitySerializer::class)
|
||||
sealed interface RichTextEntity : RichText {
|
||||
val type: String
|
||||
}
|
||||
|
||||
object RichTextSerializer : KSerializer<RichText> {
|
||||
override val descriptor: SerialDescriptor = JsonElement.serializer().descriptor
|
||||
|
||||
private fun fromJson(json: Json, element: JsonElement): RichText = when (element) {
|
||||
is JsonArray -> RichTextGroup(element.map { fromJson(json, it) })
|
||||
is JsonObject -> json.decodeFromJsonElement(RichTextEntitySerializer, element)
|
||||
is JsonPrimitive -> RichTextPlain(element.content)
|
||||
}
|
||||
|
||||
private fun toJson(json: Json, value: RichText): JsonElement = when (value) {
|
||||
is RichTextPlain -> JsonPrimitive(value.text)
|
||||
is RichTextGroup -> JsonArray(value.parts.map { toJson(json, it) })
|
||||
is RichTextEntity -> json.encodeToJsonElement(RichTextEntitySerializer, value)
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): RichText {
|
||||
val input = decoder as JsonDecoder
|
||||
return fromJson(input.json, input.decodeJsonElement())
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: RichText) {
|
||||
val output = encoder as JsonEncoder
|
||||
output.encodeJsonElement(toJson(output.json, value))
|
||||
}
|
||||
}
|
||||
|
||||
object RichTextEntitySerializer : JsonContentPolymorphicSerializer<RichTextEntity>(RichTextEntity::class) {
|
||||
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<RichTextEntity> {
|
||||
return when (val type = element.jsonObject[typeField]?.jsonPrimitive?.content) {
|
||||
RichTextBold.TYPE -> RichTextBold.serializer()
|
||||
RichTextItalic.TYPE -> RichTextItalic.serializer()
|
||||
RichTextUnderline.TYPE -> RichTextUnderline.serializer()
|
||||
RichTextStrikethrough.TYPE -> RichTextStrikethrough.serializer()
|
||||
RichTextSpoiler.TYPE -> RichTextSpoiler.serializer()
|
||||
RichTextDateTime.TYPE -> RichTextDateTime.serializer()
|
||||
RichTextTextMention.TYPE -> RichTextTextMention.serializer()
|
||||
RichTextSubscript.TYPE -> RichTextSubscript.serializer()
|
||||
RichTextSuperscript.TYPE -> RichTextSuperscript.serializer()
|
||||
RichTextMarked.TYPE -> RichTextMarked.serializer()
|
||||
RichTextCode.TYPE -> RichTextCode.serializer()
|
||||
RichTextCustomEmoji.TYPE -> RichTextCustomEmoji.serializer()
|
||||
RichTextMathematicalExpression.TYPE -> RichTextMathematicalExpression.serializer()
|
||||
RichTextUrl.TYPE -> RichTextUrl.serializer()
|
||||
RichTextEmailAddress.TYPE -> RichTextEmailAddress.serializer()
|
||||
RichTextPhoneNumber.TYPE -> RichTextPhoneNumber.serializer()
|
||||
RichTextBankCardNumber.TYPE -> RichTextBankCardNumber.serializer()
|
||||
RichTextMention.TYPE -> RichTextMention.serializer()
|
||||
RichTextHashtag.TYPE -> RichTextHashtag.serializer()
|
||||
RichTextCashtag.TYPE -> RichTextCashtag.serializer()
|
||||
RichTextBotCommand.TYPE -> RichTextBotCommand.serializer()
|
||||
RichTextAnchor.TYPE -> RichTextAnchor.serializer()
|
||||
RichTextAnchorLink.TYPE -> RichTextAnchorLink.serializer()
|
||||
RichTextReference.TYPE -> RichTextReference.serializer()
|
||||
RichTextReferenceLink.TYPE -> RichTextReferenceLink.serializer()
|
||||
else -> error("Unknown RichTextEntity type: $type")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,531 @@
|
||||
package dev.inmo.tgbotapi.types.rich
|
||||
|
||||
import dev.inmo.tgbotapi.types.CustomEmojiId
|
||||
import dev.inmo.tgbotapi.types.alternativeTextField
|
||||
import dev.inmo.tgbotapi.types.anchorNameField
|
||||
import dev.inmo.tgbotapi.types.bankCardNumberField
|
||||
import dev.inmo.tgbotapi.types.botCommandField
|
||||
import dev.inmo.tgbotapi.types.cashtagField
|
||||
import dev.inmo.tgbotapi.types.chat.User
|
||||
import dev.inmo.tgbotapi.types.customEmojiIdField
|
||||
import dev.inmo.tgbotapi.types.dateTimeFormatField
|
||||
import dev.inmo.tgbotapi.types.emailAddressField
|
||||
import dev.inmo.tgbotapi.types.expressionField
|
||||
import dev.inmo.tgbotapi.types.hashtagField
|
||||
import dev.inmo.tgbotapi.types.nameField
|
||||
import dev.inmo.tgbotapi.types.phoneNumberField
|
||||
import dev.inmo.tgbotapi.types.referenceNameField
|
||||
import dev.inmo.tgbotapi.types.textField
|
||||
import dev.inmo.tgbotapi.types.typeField
|
||||
import dev.inmo.tgbotapi.types.unixTimeField
|
||||
import dev.inmo.tgbotapi.types.urlField
|
||||
import dev.inmo.tgbotapi.types.userField
|
||||
import dev.inmo.tgbotapi.types.usernameField
|
||||
import kotlinx.serialization.EncodeDefault
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* A bold [RichTextEntity].
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtextbold">RichTextBold</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextBold(
|
||||
@SerialName(textField)
|
||||
val text: RichText
|
||||
) : RichTextEntity {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "bold"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An italicized [RichTextEntity].
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtextitalic">RichTextItalic</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextItalic(
|
||||
@SerialName(textField)
|
||||
val text: RichText
|
||||
) : RichTextEntity {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "italic"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An underlined [RichTextEntity].
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtextunderline">RichTextUnderline</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextUnderline(
|
||||
@SerialName(textField)
|
||||
val text: RichText
|
||||
) : RichTextEntity {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "underline"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A strikethrough [RichTextEntity].
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtextstrikethrough">RichTextStrikethrough</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextStrikethrough(
|
||||
@SerialName(textField)
|
||||
val text: RichText
|
||||
) : RichTextEntity {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "strikethrough"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A [RichTextEntity] covered by a spoiler.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtextspoiler">RichTextSpoiler</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextSpoiler(
|
||||
@SerialName(textField)
|
||||
val text: RichText
|
||||
) : RichTextEntity {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "spoiler"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A subscript [RichTextEntity].
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtextsubscript">RichTextSubscript</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextSubscript(
|
||||
@SerialName(textField)
|
||||
val text: RichText
|
||||
) : RichTextEntity {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "subscript"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A superscript [RichTextEntity].
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtextsuperscript">RichTextSuperscript</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextSuperscript(
|
||||
@SerialName(textField)
|
||||
val text: RichText
|
||||
) : RichTextEntity {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "superscript"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A marked [RichTextEntity].
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtextmarked">RichTextMarked</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextMarked(
|
||||
@SerialName(textField)
|
||||
val text: RichText
|
||||
) : RichTextEntity {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "marked"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A monowidth [RichTextEntity].
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtextcode">RichTextCode</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextCode(
|
||||
@SerialName(textField)
|
||||
val text: RichText
|
||||
) : RichTextEntity {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "code"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A formatted date and time [RichTextEntity].
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtextdatetime">RichTextDateTime</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextDateTime(
|
||||
@SerialName(textField)
|
||||
val text: RichText,
|
||||
@SerialName(unixTimeField)
|
||||
val unixTime: Long,
|
||||
@SerialName(dateTimeFormatField)
|
||||
val dateTimeFormat: String
|
||||
) : RichTextEntity {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "date_time"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A mention of a Telegram user by their identifier.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtexttextmention">RichTextTextMention</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextTextMention(
|
||||
@SerialName(textField)
|
||||
val text: RichText,
|
||||
@SerialName(userField)
|
||||
val user: User
|
||||
) : RichTextEntity {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "text_mention"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom emoji [RichTextEntity].
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtextcustomemoji">RichTextCustomEmoji</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextCustomEmoji(
|
||||
@SerialName(customEmojiIdField)
|
||||
val customEmojiId: CustomEmojiId,
|
||||
@SerialName(alternativeTextField)
|
||||
val alternativeText: String
|
||||
) : RichTextEntity {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "custom_emoji"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A mathematical expression in LaTeX format.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtextmathematicalexpression">RichTextMathematicalExpression</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextMathematicalExpression(
|
||||
@SerialName(expressionField)
|
||||
val expression: String
|
||||
) : RichTextEntity {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "mathematical_expression"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A [RichTextEntity] with a link.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtexturl">RichTextUrl</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextUrl(
|
||||
@SerialName(textField)
|
||||
val text: RichText,
|
||||
@SerialName(urlField)
|
||||
val url: String
|
||||
) : RichTextEntity {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "url"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A [RichTextEntity] with an email address.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtextemailaddress">RichTextEmailAddress</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextEmailAddress(
|
||||
@SerialName(textField)
|
||||
val text: RichText,
|
||||
@SerialName(emailAddressField)
|
||||
val emailAddress: String
|
||||
) : RichTextEntity {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "email_address"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A [RichTextEntity] with a phone number.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtextphonenumber">RichTextPhoneNumber</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextPhoneNumber(
|
||||
@SerialName(textField)
|
||||
val text: RichText,
|
||||
@SerialName(phoneNumberField)
|
||||
val phoneNumber: String
|
||||
) : RichTextEntity {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "phone_number"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A [RichTextEntity] with a bank card number.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtextbankcardnumber">RichTextBankCardNumber</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextBankCardNumber(
|
||||
@SerialName(textField)
|
||||
val text: RichText,
|
||||
@SerialName(bankCardNumberField)
|
||||
val bankCardNumber: String
|
||||
) : RichTextEntity {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "bank_card_number"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A mention by a username.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtextmention">RichTextMention</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextMention(
|
||||
@SerialName(textField)
|
||||
val text: RichText,
|
||||
@SerialName(usernameField)
|
||||
val username: String
|
||||
) : RichTextEntity {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "mention"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A hashtag.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtexthashtag">RichTextHashtag</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextHashtag(
|
||||
@SerialName(textField)
|
||||
val text: RichText,
|
||||
@SerialName(hashtagField)
|
||||
val hashtag: String
|
||||
) : RichTextEntity {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "hashtag"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A cashtag.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtextcashtag">RichTextCashtag</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextCashtag(
|
||||
@SerialName(textField)
|
||||
val text: RichText,
|
||||
@SerialName(cashtagField)
|
||||
val cashtag: String
|
||||
) : RichTextEntity {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "cashtag"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A bot command.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtextbotcommand">RichTextBotCommand</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextBotCommand(
|
||||
@SerialName(textField)
|
||||
val text: RichText,
|
||||
@SerialName(botCommandField)
|
||||
val botCommand: String
|
||||
) : RichTextEntity {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "bot_command"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An anchor.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtextanchor">RichTextAnchor</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextAnchor(
|
||||
@SerialName(nameField)
|
||||
val name: String
|
||||
) : RichTextEntity {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "anchor"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A link to an anchor.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtextanchorlink">RichTextAnchorLink</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextAnchorLink(
|
||||
@SerialName(textField)
|
||||
val text: RichText,
|
||||
@SerialName(anchorNameField)
|
||||
val anchorName: String
|
||||
) : RichTextEntity {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "anchor_link"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtextreference">RichTextReference</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextReference(
|
||||
@SerialName(textField)
|
||||
val text: RichText,
|
||||
@SerialName(nameField)
|
||||
val name: String
|
||||
) : RichTextEntity {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "reference"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A link to a reference.
|
||||
*
|
||||
* @see <a href="https://core.telegram.org/bots/api#richtextreferencelink">RichTextReferenceLink</a>
|
||||
*/
|
||||
@Serializable
|
||||
data class RichTextReferenceLink(
|
||||
@SerialName(textField)
|
||||
val text: RichText,
|
||||
@SerialName(referenceNameField)
|
||||
val referenceName: String
|
||||
) : RichTextEntity {
|
||||
@EncodeDefault
|
||||
@SerialName(typeField)
|
||||
override val type: String = TYPE
|
||||
|
||||
companion object {
|
||||
const val TYPE = "reference_link"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package dev.inmo.tgbotapi.types
|
||||
|
||||
import dev.inmo.tgbotapi.types.rich.*
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class RichMessageSerializationTest {
|
||||
private val json = Json { encodeDefaults = true }
|
||||
|
||||
@Test
|
||||
fun decodesRichMessageWithMixedRichText() {
|
||||
val source = """
|
||||
{
|
||||
"blocks": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"text": ["Hello ", {"type": "bold", "text": "world"}, "!"]
|
||||
},
|
||||
{"type": "heading", "text": "Title", "size": 1},
|
||||
{"type": "divider"},
|
||||
{"type": "list", "items": [
|
||||
{"label": "1", "blocks": [{"type": "paragraph", "text": "first"}]}
|
||||
]}
|
||||
],
|
||||
"is_rtl": false
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val message = json.decodeFromString(RichMessage.serializer(), source)
|
||||
assertEquals(4, message.blocks.size)
|
||||
|
||||
val paragraph = message.blocks[0] as RichBlockParagraph
|
||||
val group = paragraph.text as RichTextGroup
|
||||
assertEquals(RichTextPlain("Hello "), group.parts[0])
|
||||
assertEquals(RichTextBold(RichTextPlain("world")), group.parts[1])
|
||||
assertEquals(RichTextPlain("!"), group.parts[2])
|
||||
|
||||
val heading = message.blocks[1] as RichBlockSectionHeading
|
||||
assertEquals(RichTextPlain("Title"), heading.text)
|
||||
assertEquals(1, heading.size)
|
||||
|
||||
assertTrue(message.blocks[2] is RichBlockDivider)
|
||||
|
||||
val list = message.blocks[3] as RichBlockList
|
||||
assertEquals("1", list.items[0].label)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun roundTripsRichMessage() {
|
||||
val message = RichMessage(
|
||||
blocks = listOf(
|
||||
RichBlockParagraph(
|
||||
RichTextGroup(
|
||||
listOf(
|
||||
RichTextPlain("a "),
|
||||
RichTextItalic(RichTextPlain("b")),
|
||||
RichTextUrl(RichTextPlain("link"), "https://example.org")
|
||||
)
|
||||
)
|
||||
),
|
||||
RichBlockDivider()
|
||||
)
|
||||
)
|
||||
|
||||
val encoded = json.encodeToString(RichMessage.serializer(), message)
|
||||
val decoded = json.decodeFromString(RichMessage.serializer(), encoded)
|
||||
assertEquals(message, decoded)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user