1
0
mirror of https://github.com/InsanusMokrassar/TelegramBotAPI.git synced 2026-08-18 15:19:09 +00:00

improvements

This commit is contained in:
2026-08-16 23:42:11 +06:00
parent b913f66b1e
commit f9c634ca46
7 changed files with 432 additions and 74 deletions

View File

@@ -6,6 +6,7 @@
**Breaking changes**:
* `InputRichBlockListItem` is now a sealed interface with `Ordered` and `Unordered` implementations; ordered items require a non-null `value` and typed `LabelType`, while `LabelType.Decimals` is the default; `InputRichBlocksBuilder` now exposes separate `orderedList` and `unorderedList` DSL functions with matching item builders
* `ReplyParameters` is now a sealed interface with nested `ReplyParameters.Chat` and `ReplyParameters.Ephemeral` implementations; overloaded `ReplyParameters(...)` companion factories construct both regular-message and ephemeral-message variants
* The 4 group-family message implementations (`CommonGroupContentMessageImpl`, `CommonForumContentMessageImpl`, `CommonChannelDirectMessagesContentMessageImpl`, `CommonSuggestedChannelDirectMessagesContentMessageImpl`) gained trailing `receiverUser`/`ephemeralMessageId` primary-constructor parameters (defaulted to `null`) — positional construction of these classes must be updated
* `reply(to = ...)` overloads for the 13 ephemeral-capable senders (see below) now detect an ephemeral `to` target (`to is PossiblyEphemeralMessage && to.ephemeralMessageId != null`) and automatically address the reply through `ephemeral_message_id`, sending the outgoing message itself as ephemeral to the same receiver — this is a behavior change for any existing code that replies to a message carrying a non-null `ephemeralMessageId`
@@ -13,11 +14,13 @@
**Migration advice**: Keep `ReplyParameters(...)` for both chat-message and ephemeral-message targets; explicit `ReplyParameters.Ephemeral(...)` subtype construction also remains available; pass `receiverUser`/`ephemeralMessageId` explicitly (or rely on their `null` default) when constructing the 4 group-family message implementations positionally
* `Core`:
* (`Rich Messages`) Added `h1` through `h6` shortcuts for plain and rich-text headings in `InputRichBlocksBuilder`
* (`Rich Messages`) Replaced the flat `InputRichBlockListItem` data class with ordered/unordered variants, added the string-serialized `LabelType` hierarchy (`A`, `a`, `I`, `i`, `1`), and split the list DSL into `InputRichBlockOrderedListBuilder`/`InputRichBlockUnorderedListBuilder`
* (`Rich Messages`) Added `InputRichBlock` hierarchy with all 21 `InputRichBlock*` types (mirroring the received `RichBlock*` hierarchy and reusing `RichText`/`RichBlockCaption`/`RichBlockTableCell`), the label-less `InputRichBlockListItem` and the `InputRichBlockSerializer`; every `InputRichBlock` exposes `subBlocks` navigation
* (`Rich Messages`) Added `TelegramMediaVoiceNote` (`InputMediaVoiceNote`) and the `RichMessageMemberTelegramMedia` marker interface implemented by it, `TelegramMediaAnimation`, `TelegramMediaAudio`, `TelegramMediaPhoto` and `TelegramMediaVideo`; added `VoiceFile.toTelegramMediaVoiceNote` converters
* (`Rich Messages`) Added `InputRichMessageMedia` type (media referenced from `InputRichMessage.html`/`.markdown` via `tg://photo?id=`, `tg://video?id=` and `tg://audio?id=` links)
* (`Rich Messages`) `InputRichMessage` gained `blocks` and `media` fields ("exactly one of `html`, `markdown` or `blocks` must be used") and the `InputRichMessageBlocks` factory; `InputRichMessageHTML`/`InputRichMessageMarkdown` gained a trailing `media` parameter
* (`Rich Messages`) Added Rich Messages input DSL builders `buildInputRichBlocks`/`InputRichBlocksBuilder`, `InputRichBlockListBuilder` and the `InputRichMessageBlocks { }` builder overload (marked with the `@RichTextDsl` DSL marker)
* (`Rich Messages`) Added Rich Messages input DSL builders `buildInputRichBlocks`/`InputRichBlocksBuilder`, typed ordered/unordered list builders and the `InputRichMessageBlocks { }` builder overload (marked with the `@RichTextDsl` DSL marker)
* (`Rich Messages`) `SendRichMessage` now supports `attach://` upload of new files referenced from `InputRichMessage.blocks`/`.media`; `SendRichMessageDraft` now rejects rich messages that require direct file upload (unsupported by the method)
* (`Ephemeral Messages`) Added `EphemeralMessageId` value class, `PossiblyEphemeralMessage` (`receiverUser`/`ephemeralMessageId`) and `EphemeralMessageAction` (`receiverUserId`/`ephemeralMessageId`) abstractions
* (`Ephemeral Messages`) `RawMessage` parses `receiver_user`/`ephemeral_message_id`; the 4 group-family `Common*ContentMessage` types (`CommonGroupContentMessage`, `CommonForumContentMessage`, `CommonChannelDirectMessagesContentMessage`, `CommonSuggestedChannelDirectMessagesContentMessage`, via the shared `PotentiallyFromUserGroupContentMessage`) now also implement `PossiblyEphemeralMessage`

View File

@@ -35475,50 +35475,72 @@ public final class dev/inmo/tgbotapi/types/rich/InputRichBlockList$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class dev/inmo/tgbotapi/types/rich/InputRichBlockListBuilder {
public fun <init> ()V
public final fun build ()Ljava/util/List;
public final fun item (Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Integer;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
public final fun item (Ljava/lang/String;)V
public static synthetic fun item$default (Ldev/inmo/tgbotapi/types/rich/InputRichBlockListBuilder;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Integer;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
}
public final class dev/inmo/tgbotapi/types/rich/InputRichBlockListItem {
public abstract interface class dev/inmo/tgbotapi/types/rich/InputRichBlockListItem {
public static final field Companion Ldev/inmo/tgbotapi/types/rich/InputRichBlockListItem$Companion;
public fun <init> (Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Integer;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Integer;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/util/List;
public final fun component2 ()Ljava/lang/Boolean;
public final fun component3 ()Ljava/lang/Boolean;
public final fun component4 ()Ljava/lang/Integer;
public final fun component5 ()Ljava/lang/String;
public final fun copy (Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Integer;Ljava/lang/String;)Ldev/inmo/tgbotapi/types/rich/InputRichBlockListItem;
public static synthetic fun copy$default (Ldev/inmo/tgbotapi/types/rich/InputRichBlockListItem;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Integer;Ljava/lang/String;ILjava/lang/Object;)Ldev/inmo/tgbotapi/types/rich/InputRichBlockListItem;
public fun equals (Ljava/lang/Object;)Z
public final fun getBlocks ()Ljava/util/List;
public final fun getHasCheckbox ()Ljava/lang/Boolean;
public final fun getLabelType ()Ljava/lang/String;
public final fun getValue ()Ljava/lang/Integer;
public fun hashCode ()I
public final fun isChecked ()Ljava/lang/Boolean;
public fun toString ()Ljava/lang/String;
}
public final synthetic class dev/inmo/tgbotapi/types/rich/InputRichBlockListItem$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Ldev/inmo/tgbotapi/types/rich/InputRichBlockListItem$$serializer;
public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/inmo/tgbotapi/types/rich/InputRichBlockListItem;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/inmo/tgbotapi/types/rich/InputRichBlockListItem;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
public abstract fun getBlocks ()Ljava/util/List;
public abstract fun getHasCheckbox ()Ljava/lang/Boolean;
public abstract fun isChecked ()Ljava/lang/Boolean;
}
public final class dev/inmo/tgbotapi/types/rich/InputRichBlockListItem$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class dev/inmo/tgbotapi/types/rich/InputRichBlockListItem$Ordered : dev/inmo/tgbotapi/types/rich/InputRichBlockListItem {
public static final field Companion Ldev/inmo/tgbotapi/types/rich/InputRichBlockListItem$Ordered$Companion;
public fun <init> (Ljava/util/List;ILdev/inmo/tgbotapi/types/rich/LabelType;Ljava/lang/Boolean;Ljava/lang/Boolean;)V
public synthetic fun <init> (Ljava/util/List;ILdev/inmo/tgbotapi/types/rich/LabelType;Ljava/lang/Boolean;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/util/List;
public final fun component2 ()I
public final fun component3 ()Ldev/inmo/tgbotapi/types/rich/LabelType;
public final fun component4 ()Ljava/lang/Boolean;
public final fun component5 ()Ljava/lang/Boolean;
public final fun copy (Ljava/util/List;ILdev/inmo/tgbotapi/types/rich/LabelType;Ljava/lang/Boolean;Ljava/lang/Boolean;)Ldev/inmo/tgbotapi/types/rich/InputRichBlockListItem$Ordered;
public static synthetic fun copy$default (Ldev/inmo/tgbotapi/types/rich/InputRichBlockListItem$Ordered;Ljava/util/List;ILdev/inmo/tgbotapi/types/rich/LabelType;Ljava/lang/Boolean;Ljava/lang/Boolean;ILjava/lang/Object;)Ldev/inmo/tgbotapi/types/rich/InputRichBlockListItem$Ordered;
public fun equals (Ljava/lang/Object;)Z
public fun getBlocks ()Ljava/util/List;
public fun getHasCheckbox ()Ljava/lang/Boolean;
public final fun getLabelType ()Ldev/inmo/tgbotapi/types/rich/LabelType;
public final fun getValue ()I
public fun hashCode ()I
public fun isChecked ()Ljava/lang/Boolean;
public fun toString ()Ljava/lang/String;
}
public final class dev/inmo/tgbotapi/types/rich/InputRichBlockListItem$Ordered$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class dev/inmo/tgbotapi/types/rich/InputRichBlockListItem$Serializer : kotlinx/serialization/KSerializer {
public static final field INSTANCE Ldev/inmo/tgbotapi/types/rich/InputRichBlockListItem$Serializer;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/inmo/tgbotapi/types/rich/InputRichBlockListItem;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/inmo/tgbotapi/types/rich/InputRichBlockListItem;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
}
public final class dev/inmo/tgbotapi/types/rich/InputRichBlockListItem$Unordered : dev/inmo/tgbotapi/types/rich/InputRichBlockListItem {
public static final field Companion Ldev/inmo/tgbotapi/types/rich/InputRichBlockListItem$Unordered$Companion;
public fun <init> (Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;)V
public synthetic fun <init> (Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/util/List;
public final fun component2 ()Ljava/lang/Boolean;
public final fun component3 ()Ljava/lang/Boolean;
public final fun copy (Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;)Ldev/inmo/tgbotapi/types/rich/InputRichBlockListItem$Unordered;
public static synthetic fun copy$default (Ldev/inmo/tgbotapi/types/rich/InputRichBlockListItem$Unordered;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;ILjava/lang/Object;)Ldev/inmo/tgbotapi/types/rich/InputRichBlockListItem$Unordered;
public fun equals (Ljava/lang/Object;)Z
public fun getBlocks ()Ljava/util/List;
public fun getHasCheckbox ()Ljava/lang/Boolean;
public fun hashCode ()I
public fun isChecked ()Ljava/lang/Boolean;
public fun toString ()Ljava/lang/String;
}
public final class dev/inmo/tgbotapi/types/rich/InputRichBlockListItem$Unordered$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class dev/inmo/tgbotapi/types/rich/InputRichBlockMap : dev/inmo/tgbotapi/types/rich/InputRichBlock {
public static final field Companion Ldev/inmo/tgbotapi/types/rich/InputRichBlockMap$Companion;
public static final field TYPE Ljava/lang/String;
@@ -35596,6 +35618,13 @@ public final class dev/inmo/tgbotapi/types/rich/InputRichBlockMedia$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class dev/inmo/tgbotapi/types/rich/InputRichBlockOrderedListBuilder {
public fun <init> ()V
public final fun build ()Ljava/util/List;
public final fun item (ILdev/inmo/tgbotapi/types/rich/LabelType;Ljava/lang/Boolean;Ljava/lang/Boolean;Lkotlin/jvm/functions/Function1;)V
public static synthetic fun item$default (Ldev/inmo/tgbotapi/types/rich/InputRichBlockOrderedListBuilder;ILdev/inmo/tgbotapi/types/rich/LabelType;Ljava/lang/Boolean;Ljava/lang/Boolean;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
}
public final class dev/inmo/tgbotapi/types/rich/InputRichBlockParagraph : dev/inmo/tgbotapi/types/rich/InputRichBlock {
public static final field Companion Ldev/inmo/tgbotapi/types/rich/InputRichBlockParagraph$Companion;
public static final field TYPE Ljava/lang/String;
@@ -35854,6 +35883,14 @@ public final class dev/inmo/tgbotapi/types/rich/InputRichBlockThinking$Companion
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class dev/inmo/tgbotapi/types/rich/InputRichBlockUnorderedListBuilder {
public fun <init> ()V
public final fun build ()Ljava/util/List;
public final fun item (Ljava/lang/Boolean;Ljava/lang/Boolean;Lkotlin/jvm/functions/Function1;)V
public final fun item (Ljava/lang/String;)V
public static synthetic fun item$default (Ldev/inmo/tgbotapi/types/rich/InputRichBlockUnorderedListBuilder;Ljava/lang/Boolean;Ljava/lang/Boolean;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
}
public final class dev/inmo/tgbotapi/types/rich/InputRichBlockVideo : dev/inmo/tgbotapi/types/rich/InputRichBlockMedia {
public static final field Companion Ldev/inmo/tgbotapi/types/rich/InputRichBlockVideo$Companion;
public static final field TYPE Ljava/lang/String;
@@ -35940,12 +35977,24 @@ public final class dev/inmo/tgbotapi/types/rich/InputRichBlocksBuilder {
public final fun divider ()V
public final fun footer (Ljava/lang/String;)V
public final fun footer (Lkotlin/jvm/functions/Function1;)V
public final fun h1 (Ljava/lang/String;)V
public final fun h1 (Lkotlin/jvm/functions/Function1;)V
public final fun h2 (Ljava/lang/String;)V
public final fun h2 (Lkotlin/jvm/functions/Function1;)V
public final fun h3 (Ljava/lang/String;)V
public final fun h3 (Lkotlin/jvm/functions/Function1;)V
public final fun h4 (Ljava/lang/String;)V
public final fun h4 (Lkotlin/jvm/functions/Function1;)V
public final fun h5 (Ljava/lang/String;)V
public final fun h5 (Lkotlin/jvm/functions/Function1;)V
public final fun h6 (Ljava/lang/String;)V
public final fun h6 (Lkotlin/jvm/functions/Function1;)V
public final fun heading (ILkotlin/jvm/functions/Function1;)V
public final fun heading (Ljava/lang/String;I)V
public final fun list (Lkotlin/jvm/functions/Function1;)V
public final fun map (Ldev/inmo/tgbotapi/types/location/StaticLocation;IIILdev/inmo/tgbotapi/types/rich/RichBlockCaption;)V
public static synthetic fun map$default (Ldev/inmo/tgbotapi/types/rich/InputRichBlocksBuilder;Ldev/inmo/tgbotapi/types/location/StaticLocation;IIILdev/inmo/tgbotapi/types/rich/RichBlockCaption;ILjava/lang/Object;)V
public final fun mathematicalExpression (Ljava/lang/String;)V
public final fun orderedList (Lkotlin/jvm/functions/Function1;)V
public final fun paragraph (Ljava/lang/String;)V
public final fun paragraph (Lkotlin/jvm/functions/Function1;)V
public final fun photo (Ldev/inmo/tgbotapi/types/media/TelegramMediaPhoto;Ldev/inmo/tgbotapi/types/rich/RichBlockCaption;)V
@@ -35961,6 +36010,7 @@ public final class dev/inmo/tgbotapi/types/rich/InputRichBlocksBuilder {
public final fun thinking (Ljava/lang/String;)V
public final fun thinking (Lkotlin/jvm/functions/Function1;)V
public final fun unaryPlus (Ldev/inmo/tgbotapi/types/rich/InputRichBlock;)V
public final fun unorderedList (Lkotlin/jvm/functions/Function1;)V
public final fun video (Ldev/inmo/tgbotapi/types/media/TelegramMediaVideo;Ldev/inmo/tgbotapi/types/rich/RichBlockCaption;)V
public static synthetic fun video$default (Ldev/inmo/tgbotapi/types/rich/InputRichBlocksBuilder;Ldev/inmo/tgbotapi/types/media/TelegramMediaVideo;Ldev/inmo/tgbotapi/types/rich/RichBlockCaption;ILjava/lang/Object;)V
public final fun voiceNote (Ldev/inmo/tgbotapi/types/media/TelegramMediaVoiceNote;Ldev/inmo/tgbotapi/types/rich/RichBlockCaption;)V
@@ -36046,6 +36096,69 @@ public final class dev/inmo/tgbotapi/types/rich/InputRichMessageMedia$Companion
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public abstract interface class dev/inmo/tgbotapi/types/rich/LabelType {
public static final field Companion Ldev/inmo/tgbotapi/types/rich/LabelType$Companion;
public abstract fun getTypeSymbol ()Ljava/lang/String;
}
public final class dev/inmo/tgbotapi/types/rich/LabelType$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class dev/inmo/tgbotapi/types/rich/LabelType$Decimals : dev/inmo/tgbotapi/types/rich/LabelType {
public static final field INSTANCE Ldev/inmo/tgbotapi/types/rich/LabelType$Decimals;
public fun equals (Ljava/lang/Object;)Z
public fun getTypeSymbol ()Ljava/lang/String;
public fun hashCode ()I
public final fun serializer ()Lkotlinx/serialization/KSerializer;
public fun toString ()Ljava/lang/String;
}
public final class dev/inmo/tgbotapi/types/rich/LabelType$LettersLowercase : dev/inmo/tgbotapi/types/rich/LabelType {
public static final field INSTANCE Ldev/inmo/tgbotapi/types/rich/LabelType$LettersLowercase;
public fun equals (Ljava/lang/Object;)Z
public fun getTypeSymbol ()Ljava/lang/String;
public fun hashCode ()I
public final fun serializer ()Lkotlinx/serialization/KSerializer;
public fun toString ()Ljava/lang/String;
}
public final class dev/inmo/tgbotapi/types/rich/LabelType$LettersUppercase : dev/inmo/tgbotapi/types/rich/LabelType {
public static final field INSTANCE Ldev/inmo/tgbotapi/types/rich/LabelType$LettersUppercase;
public fun equals (Ljava/lang/Object;)Z
public fun getTypeSymbol ()Ljava/lang/String;
public fun hashCode ()I
public final fun serializer ()Lkotlinx/serialization/KSerializer;
public fun toString ()Ljava/lang/String;
}
public final class dev/inmo/tgbotapi/types/rich/LabelType$RomanLowercase : dev/inmo/tgbotapi/types/rich/LabelType {
public static final field INSTANCE Ldev/inmo/tgbotapi/types/rich/LabelType$RomanLowercase;
public fun equals (Ljava/lang/Object;)Z
public fun getTypeSymbol ()Ljava/lang/String;
public fun hashCode ()I
public final fun serializer ()Lkotlinx/serialization/KSerializer;
public fun toString ()Ljava/lang/String;
}
public final class dev/inmo/tgbotapi/types/rich/LabelType$RomanUppercase : dev/inmo/tgbotapi/types/rich/LabelType {
public static final field INSTANCE Ldev/inmo/tgbotapi/types/rich/LabelType$RomanUppercase;
public fun equals (Ljava/lang/Object;)Z
public fun getTypeSymbol ()Ljava/lang/String;
public fun hashCode ()I
public final fun serializer ()Lkotlinx/serialization/KSerializer;
public fun toString ()Ljava/lang/String;
}
public final class dev/inmo/tgbotapi/types/rich/LabelType$Serializer : kotlinx/serialization/KSerializer {
public static final field INSTANCE Ldev/inmo/tgbotapi/types/rich/LabelType$Serializer;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/inmo/tgbotapi/types/rich/LabelType;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/inmo/tgbotapi/types/rich/LabelType;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
}
public abstract interface class dev/inmo/tgbotapi/types/rich/RichBlock {
public static final field Companion Ldev/inmo/tgbotapi/types/rich/RichBlock$Companion;
public abstract fun getHtml ()Ljava/lang/String;

View File

@@ -5,27 +5,100 @@ import dev.inmo.tgbotapi.types.hasCheckboxField
import dev.inmo.tgbotapi.types.isCheckedField
import dev.inmo.tgbotapi.types.typeField
import dev.inmo.tgbotapi.types.valueField
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
/**
* An item of an [InputRichBlockList].
* An item of an [InputRichBlockList]. Ordered items have a non-null [InputRichBlockListItem.Ordered.value] and
* [InputRichBlockListItem.Ordered.labelType]; unordered items omit both fields from the Bot API payload.
*
* @see <a href="https://core.telegram.org/bots/api#inputrichblocklistitem">InputRichBlockListItem</a>
*/
@Serializable
data class InputRichBlockListItem(
@SerialName(blocksField)
val blocks: List<InputRichBlock>,
@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
)
@Serializable(InputRichBlockListItem.Serializer::class)
sealed interface InputRichBlockListItem {
val blocks: List<InputRichBlock>
val hasCheckbox: Boolean?
val isChecked: Boolean?
@Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
@Serializable(Serializer::class)
data class Ordered(
@SerialName(blocksField)
override val blocks: List<InputRichBlock>,
@SerialName(valueField)
val value: Int,
@SerialName(typeField)
val labelType: LabelType = LabelType.Decimals,
@SerialName(hasCheckboxField)
override val hasCheckbox: Boolean? = null,
@SerialName(isCheckedField)
override val isChecked: Boolean? = null
) : InputRichBlockListItem
@Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
@Serializable(Serializer::class)
data class Unordered(
@SerialName(blocksField)
override val blocks: List<InputRichBlock>,
@SerialName(hasCheckboxField)
override val hasCheckbox: Boolean? = null,
@SerialName(isCheckedField)
override val isChecked: Boolean? = null
) : InputRichBlockListItem
object Serializer : KSerializer<InputRichBlockListItem> {
@Serializable
private data class Surrogate(
@SerialName(blocksField)
val blocks: List<InputRichBlock>,
@SerialName(hasCheckboxField)
val hasCheckbox: Boolean? = null,
@SerialName(isCheckedField)
val isChecked: Boolean? = null,
@SerialName(valueField)
val value: Int? = null,
@SerialName(typeField)
val labelType: LabelType? = null
)
override val descriptor: SerialDescriptor = Surrogate.serializer().descriptor
override fun deserialize(decoder: Decoder): InputRichBlockListItem {
val surrogate = decoder.decodeSerializableValue(Surrogate.serializer())
return when {
surrogate.value != null && surrogate.labelType != null -> Ordered(
surrogate.blocks,
surrogate.value,
surrogate.labelType,
surrogate.hasCheckbox,
surrogate.isChecked
)
surrogate.value == null && surrogate.labelType == null -> Unordered(
surrogate.blocks,
surrogate.hasCheckbox,
surrogate.isChecked
)
else -> throw SerializationException("Ordered InputRichBlockListItem requires both value and type")
}
}
override fun serialize(encoder: Encoder, value: InputRichBlockListItem) {
val surrogate = when (value) {
is Ordered -> Surrogate(
value.blocks,
value.hasCheckbox,
value.isChecked,
value.value,
value.labelType
)
is Unordered -> Surrogate(value.blocks, value.hasCheckbox, value.isChecked)
}
encoder.encodeSerializableValue(Surrogate.serializer(), surrogate)
}
}
}

View File

@@ -8,27 +8,45 @@ import dev.inmo.tgbotapi.types.media.TelegramMediaVideo
import dev.inmo.tgbotapi.types.media.TelegramMediaVoiceNote
/**
* Builder of [InputRichBlockListItem]s used inside [InputRichBlocksBuilder.list].
* Builder of [InputRichBlockListItem.Ordered] items used inside [InputRichBlocksBuilder.orderedList].
*/
@RichTextDsl
class InputRichBlockListBuilder {
private val items = mutableListOf<InputRichBlockListItem>()
class InputRichBlockOrderedListBuilder {
private val items = mutableListOf<InputRichBlockListItem.Ordered>()
fun item(
value: Int,
labelType: LabelType = LabelType.Decimals,
hasCheckbox: Boolean? = null,
isChecked: Boolean? = null,
block: InputRichBlocksBuilder.() -> Unit
) {
items.add(InputRichBlockListItem.Ordered(buildInputRichBlocks(block), value, labelType, hasCheckbox, isChecked))
}
fun build(): List<InputRichBlockListItem.Ordered> = items.toList()
}
/**
* Builder of [InputRichBlockListItem.Unordered] items used inside [InputRichBlocksBuilder.unorderedList].
*/
@RichTextDsl
class InputRichBlockUnorderedListBuilder {
private val items = mutableListOf<InputRichBlockListItem.Unordered>()
fun item(
hasCheckbox: Boolean? = null,
isChecked: Boolean? = null,
value: Int? = null,
labelType: String? = null,
block: InputRichBlocksBuilder.() -> Unit
) {
items.add(InputRichBlockListItem(buildInputRichBlocks(block), hasCheckbox, isChecked, value, labelType))
items.add(InputRichBlockListItem.Unordered(buildInputRichBlocks(block), hasCheckbox, isChecked))
}
fun item(text: String) {
items.add(InputRichBlockListItem(listOf(InputRichBlockParagraph(RichTextPlain(text)))))
items.add(InputRichBlockListItem.Unordered(listOf(InputRichBlockParagraph(RichTextPlain(text)))))
}
fun build(): List<InputRichBlockListItem> = items.toList()
fun build(): List<InputRichBlockListItem.Unordered> = items.toList()
}
/**
@@ -54,6 +72,19 @@ class InputRichBlocksBuilder {
fun heading(text: String, level: Int) = add(InputRichBlockSectionHeading(RichTextPlain(text), level))
fun heading(level: Int, block: RichTextBuilder.() -> Unit) = add(InputRichBlockSectionHeading(buildRichText(block), level))
fun h1(text: String) = heading(text, 1)
fun h1(block: RichTextBuilder.() -> Unit) = heading(1, block)
fun h2(text: String) = heading(text, 2)
fun h2(block: RichTextBuilder.() -> Unit) = heading(2, block)
fun h3(text: String) = heading(text, 3)
fun h3(block: RichTextBuilder.() -> Unit) = heading(3, block)
fun h4(text: String) = heading(text, 4)
fun h4(block: RichTextBuilder.() -> Unit) = heading(4, block)
fun h5(text: String) = heading(text, 5)
fun h5(block: RichTextBuilder.() -> Unit) = heading(5, block)
fun h6(text: String) = heading(text, 6)
fun h6(block: RichTextBuilder.() -> Unit) = heading(6, block)
fun preformatted(text: String, language: String? = null) = add(InputRichBlockPreformatted(RichTextPlain(text), language))
fun footer(text: String) = add(InputRichBlockFooter(RichTextPlain(text)))
@@ -68,7 +99,11 @@ class InputRichBlocksBuilder {
fun thinking(text: String) = add(InputRichBlockThinking(RichTextPlain(text)))
fun thinking(block: RichTextBuilder.() -> Unit) = add(InputRichBlockThinking(buildRichText(block)))
fun list(block: InputRichBlockListBuilder.() -> Unit) = add(InputRichBlockList(InputRichBlockListBuilder().apply(block).build()))
fun orderedList(block: InputRichBlockOrderedListBuilder.() -> Unit) =
add(InputRichBlockList(InputRichBlockOrderedListBuilder().apply(block).build()))
fun unorderedList(block: InputRichBlockUnorderedListBuilder.() -> Unit) =
add(InputRichBlockList(InputRichBlockUnorderedListBuilder().apply(block).build()))
fun blockQuotation(credit: RichText? = null, block: InputRichBlocksBuilder.() -> Unit) =
add(InputRichBlockBlockQuotation(buildInputRichBlocks(block), credit))

View File

@@ -0,0 +1,65 @@
package dev.inmo.tgbotapi.types.rich
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
/**
* Type of an ordered [InputRichBlockListItem] label.
*/
@Serializable(LabelType.Serializer::class)
sealed interface LabelType {
val typeSymbol: String
@Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
@Serializable(Serializer::class)
data object LettersUppercase : LabelType {
override val typeSymbol: String = "A"
}
@Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
@Serializable(Serializer::class)
data object LettersLowercase : LabelType {
override val typeSymbol: String = "a"
}
@Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
@Serializable(Serializer::class)
data object RomanUppercase : LabelType {
override val typeSymbol: String = "I"
}
@Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
@Serializable(Serializer::class)
data object RomanLowercase : LabelType {
override val typeSymbol: String = "i"
}
@Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
@Serializable(Serializer::class)
data object Decimals : LabelType {
override val typeSymbol: String = "1"
}
object Serializer : KSerializer<LabelType> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LabelType", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): LabelType = when (val typeSymbol = decoder.decodeString()) {
LettersUppercase.typeSymbol -> LettersUppercase
LettersLowercase.typeSymbol -> LettersLowercase
RomanUppercase.typeSymbol -> RomanUppercase
RomanLowercase.typeSymbol -> RomanLowercase
Decimals.typeSymbol -> Decimals
else -> throw SerializationException("Unknown InputRichBlockListItem label type: $typeSymbol")
}
override fun serialize(encoder: Encoder, value: LabelType) {
encoder.encodeString(value.typeSymbol)
}
}
}

View File

@@ -15,16 +15,19 @@ class InputRichBlocksDslTest {
bold("world")
}
divider()
list {
unorderedList {
item("first")
item(labelType = "1") { paragraph("second") }
item { paragraph("second") }
}
orderedList {
item(value = 2) { paragraph("third") }
}
blockQuotation {
paragraph("quoted")
}
}
assertEquals(5, blocks.size)
assertEquals(6, blocks.size)
assertEquals(InputRichBlockSectionHeading(RichTextPlain("Title"), 1), blocks[0])
assertEquals(
InputRichBlockParagraph(RichTextGroup(listOf(RichTextPlain("Hello "), RichTextBold(RichTextPlain("world"))))),
@@ -34,10 +37,13 @@ class InputRichBlocksDslTest {
val list = blocks[3] as InputRichBlockList
assertEquals(2, list.items.size)
assertEquals(InputRichBlockListItem(listOf(InputRichBlockParagraph(RichTextPlain("first")))), list.items[0])
assertEquals("1", list.items[1].labelType)
assertEquals(InputRichBlockListItem.Unordered(listOf(InputRichBlockParagraph(RichTextPlain("first")))), list.items[0])
assertEquals(InputRichBlockListItem.Unordered::class, list.items[1]::class)
assertEquals(InputRichBlockBlockQuotation(listOf(InputRichBlockParagraph(RichTextPlain("quoted")))), blocks[4])
val orderedList = blocks[4] as InputRichBlockList
assertEquals(LabelType.Decimals, (orderedList.items.single() as InputRichBlockListItem.Ordered).labelType)
assertEquals(InputRichBlockBlockQuotation(listOf(InputRichBlockParagraph(RichTextPlain("quoted")))), blocks[5])
}
@Test
@@ -56,4 +62,20 @@ class InputRichBlocksDslTest {
}
assertEquals(InputRichMessageBlocks(listOf(InputRichBlockParagraph(RichTextPlain("p"))), true), message)
}
@Test
fun buildsHeadingShortcuts() {
val blocks = buildInputRichBlocks {
h1("h1")
h2 { bold("h2") }
h3("h3")
h4 { plain("h4") }
h5("h5")
h6 { italic("h6") }
}
assertEquals((1..6).toList(), blocks.map { (it as InputRichBlockSectionHeading).level })
assertEquals(RichTextPlain("h1"), (blocks[0] as InputRichBlockSectionHeading).text)
assertEquals(RichTextBold(RichTextPlain("h2")), (blocks[1] as InputRichBlockSectionHeading).text)
}
}

View File

@@ -4,6 +4,7 @@ import dev.inmo.tgbotapi.requests.abstracts.FileId
import dev.inmo.tgbotapi.types.media.TelegramMediaPhoto
import dev.inmo.tgbotapi.types.media.TelegramMediaVoiceNote
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
@@ -27,9 +28,9 @@ class InputRichMessageSerializationTest {
InputRichBlockDivider(),
InputRichBlockList(
listOf(
InputRichBlockListItem(
InputRichBlockListItem.Ordered(
listOf(InputRichBlockParagraph(RichTextPlain("first"))),
labelType = "1"
value = 1
)
)
)
@@ -117,7 +118,7 @@ class InputRichMessageSerializationTest {
InputRichBlockDivider(),
InputRichBlockList(
listOf(
InputRichBlockListItem(
InputRichBlockListItem.Unordered(
listOf(InputRichBlockParagraph(RichTextPlain("item"))),
hasCheckbox = true,
isChecked = true
@@ -132,6 +133,52 @@ class InputRichMessageSerializationTest {
assertEquals(blocks, decoded)
}
@Test
fun serializesAndDeserializesListItemVariantsWithFlatShape() {
val ordered: InputRichBlockListItem = InputRichBlockListItem.Ordered(
blocks = emptyList(),
value = 4,
labelType = LabelType.RomanUppercase
)
val unordered: InputRichBlockListItem = InputRichBlockListItem.Unordered(blocks = emptyList())
val orderedElement = json.encodeToJsonElement(InputRichBlockListItem.Serializer, ordered).jsonObject
assertEquals(4, orderedElement["value"]?.jsonPrimitive?.int)
assertEquals("I", orderedElement["type"]?.jsonPrimitive?.content)
assertEquals(ordered, json.decodeFromJsonElement(InputRichBlockListItem.Serializer, orderedElement))
assertEquals(unordered, json.decodeFromString(InputRichBlockListItem.Serializer, "{\"blocks\":[]}"))
}
@Test
fun serializesEveryLabelTypeAsItsSymbol() {
val labelTypes = listOf(
LabelType.LettersUppercase,
LabelType.LettersLowercase,
LabelType.RomanUppercase,
LabelType.RomanLowercase,
LabelType.Decimals
)
labelTypes.forEach { labelType ->
val encoded = json.encodeToString(LabelType.Serializer, labelType)
assertEquals("\"${labelType.typeSymbol}\"", encoded)
assertEquals(labelType, json.decodeFromString(LabelType.Serializer, encoded))
}
}
@Test
fun rejectsIncompleteOrderedListItemsAndUnknownLabelTypes() {
assertFailsWith<SerializationException> {
json.decodeFromString(InputRichBlockListItem.Serializer, "{\"blocks\":[],\"value\":1}")
}
assertFailsWith<SerializationException> {
json.decodeFromString(InputRichBlockListItem.Serializer, "{\"blocks\":[],\"type\":\"1\"}")
}
assertFailsWith<SerializationException> {
json.decodeFromString(LabelType.Serializer, "\"?\"")
}
}
@Test
fun requiresExactlyOneOfHtmlMarkdownOrBlocks() {
assertFailsWith<IllegalArgumentException> {