Skip to content

Commit 15ff4c4

Browse files
rubenlagusclaude
andcommitted
Api Version 10.3 (#1606)
* fix(richtext): serialize RichTextPlain as a bare JSON string The Bot API defines RichText as "either a String for plain text, an Array of RichText, or any of the following types" - there is no "plain" type on the wire. RichTextPlain exists only to give plain text a place in the Java RichText hierarchy. RichTextDeserializer already mapped a bare JSON string to RichTextPlain on the way in, but nothing handled the way out: RichTextPlain had no custom serializer, so Jackson wrote it as {"type":"plain","text":...}. Telegram rejected such payloads with: Bad Request: Can't parse PageBlockTableCell: Unsupported rich text type Add RichTextPlainSerializer, mirroring the existing RichTextConcatSerializer for the other library-only pseudo-type. It writes the bare string and overrides serializeWithType so the @JsonTypeInfo machinery on the RichText interface cannot re-wrap it in a type envelope. Reading {"type":"plain",...} keeps working, so JSON persisted by the old serializer still deserializes. Fixes #1599 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Bug fixing v10.2.1 * feat(10.3): add EphemeralMessageParameters Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(replykeyboard): resolve ReplyKeyboard subtypes by marker key instead of deduction Bot API 10.3 gives ReplyKeyboardMarkup a force_reply field, which makes ForceReplyKeyboard's property set a subset of it. Jackson's Id.DEDUCTION then cannot discriminate and, with defaultImpl = Void.class, returns null instead of throwing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(10.3): add DisabledButton, InlineKeyboardButton.disabled and force_reply Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(10.3): add RichMessageButton and RichTextButton Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(10.3): add buttons, expandable_blockquote and document rich blocks Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(10.3): add can_send_welcome_messages admin right Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(10.3): add CommunityChatJoined service message Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(10.3): add stopped_message_generation update Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(10.3): add text, entities and is_private to UniqueGiftInfo Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(client): send suggested_post_parameters as JSON, not toString() addPart(String, Object) stringifies via value.toString(), so the nested object was reaching the wire as Lombok's SuggestedPostParameters(price=..., ...). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(10.3)!: replace receiver_user_id/callback_query_id with ephemeral_message_parameters The two flat parameters are kept as deprecated write-through accessors and builder methods so 10.2 code still compiles; only the nested object is sent. Also teaches telegrambots-abilities about the new stopped_message_generation update, whose Update.has* method is enumerated reflectively by AbilityUtils. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(10.3): add rich_message to editEphemeralMessageText and show_caption_above_media to editEphemeralMessageCaption Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(10.3)!: support new file upload in editEphemeralMessageMedia EditEphemeralMessageMedia becomes a PartialBotApiMethod<Boolean> so the media can be sent as multipart, with matching client overloads in both client modules. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(10.3): add can_stop and keep_on_stop to the draft methods Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: bump version to 10.3.0 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * build(surefire): run suffix-named *Test.java classes The surefire <includes> block only listed **/Test*.java, so suffix-named test classes were never executed by a normal build. Four files were dormant: FlagTest 9 tests AbilityUtilsTest 13 tests UpdateTypeTest 24 tests OkHttpFutureCallbackTest 1 test All 47 pass once included. The same block also nested <forkCount>0</forkCount> inside <includes>, where Maven parsed it as an include pattern named "0" and ignored it. It is removed rather than relocated: hoisting it to <configuration> actually applies it, and in-process execution runs with the reactor root as the working directory, which breaks TestPartialBotApiMethod because it resolves src/test/resources/fixtures/... relatively. Dropping the line preserves the forked default (forkCount=1) and keeps the module as the working directory. Applying forkCount=0 requires fixing that relative path first. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(10.3): cover stopped-generation branches and is_compact serialization Three code paths added for Bot API 10.3 had no test exercising them. Flag.STOPPED_MESSAGE_GENERATION and the two AbilityUtils branches it feeds (getChatId, and the getUser fall-through to EMPTY_USER) were only reachable through the abilities module, which the plan's file map did not cover. Removing either branch now fails the new tests with IllegalStateException rather than passing silently. InputRichBlockTable.isCompact was asserted only in the negative — omitted when unset. It is an outbound object, so emitting "is_compact":true is the direction that reaches Telegram; that is now asserted and round-tripped. The inbound RichBlockTable case was already covered. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent b0edb54 commit 15ff4c4

90 files changed

Lines changed: 3306 additions & 219 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎pom.xml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<groupId>org.telegram</groupId>
88
<artifactId>Bots</artifactId>
99
<packaging>pom</packaging>
10-
<version>10.2.1</version>
10+
<version>10.3.0</version>
1111

1212
<modules>
1313
<module>telegrambots-meta</module>
@@ -296,7 +296,7 @@
296296
<configuration>
297297
<includes>
298298
<include>**/Test*.java</include>
299-
<forkCount>0</forkCount>
299+
<include>**/*Test.java</include>
300300
</includes>
301301
</configuration>
302302
</plugin>

‎telegrambots-abilities/pom.xml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<parent>
88
<groupId>org.telegram</groupId>
99
<artifactId>Bots</artifactId>
10-
<version>10.2.1</version>
10+
<version>10.3.0</version>
1111
</parent>
1212

1313
<artifactId>telegrambots-abilities</artifactId>
@@ -85,12 +85,12 @@
8585
<dependency>
8686
<groupId>org.telegram</groupId>
8787
<artifactId>telegrambots-webhook</artifactId>
88-
<version>10.2.1</version>
88+
<version>10.3.0</version>
8989
</dependency>
9090
<dependency>
9191
<groupId>org.telegram</groupId>
9292
<artifactId>telegrambots-longpolling</artifactId>
93-
<version>10.2.1</version>
93+
<version>10.3.0</version>
9494
</dependency>
9595

9696
<dependency>

‎telegrambots-abilities/src/main/java/org/telegram/telegrambots/abilitybots/api/objects/Flag.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public enum Flag implements Predicate<Update> {
4444
REMOVED_CHAT_BOOST(Update::hasRemovedChatBoost),
4545
GUEST_MESSAGE(Update::hasGuestMessage),
4646
HAS_SUBSCRIPTION(Update::hasSubscription),
47+
STOPPED_MESSAGE_GENERATION(Update::hasStoppedMessageGeneration),
4748

4849

4950
// Message Flags

‎telegrambots-abilities/src/main/java/org/telegram/telegrambots/abilitybots/api/util/AbilityUtils.java‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ private static User getUserElseThrow(Update update) {
111111
return defaultIfNull(update.getRemovedChatBoost().getSource().getUser(), EMPTY_USER);
112112
} else if (Flag.GUEST_MESSAGE.test(update)) {
113113
return update.getGuestMessage().getFrom();
114-
} else if (Flag.POLL.test(update) || Flag.MESSAGE_REACTION_COUNT.test(update)) {
114+
} else if (Flag.POLL.test(update) || Flag.MESSAGE_REACTION_COUNT.test(update)
115+
|| Flag.STOPPED_MESSAGE_GENERATION.test(update)) {
115116
return EMPTY_USER;
116117
} else {
117118
throw new IllegalStateException("Could not retrieve originating user from update");
@@ -226,6 +227,8 @@ public static Long getChatId(Update update) {
226227
return update.getChatBoost().getChat().getId();
227228
} else if (Flag.REMOVED_CHAT_BOOST.test(update)) {
228229
return update.getRemovedChatBoost().getChat().getId();
230+
} else if (Flag.STOPPED_MESSAGE_GENERATION.test(update)) {
231+
return update.getStoppedMessageGeneration().getChat().getId();
229232
} else {
230233
throw new IllegalStateException("Could not retrieve originating chat ID from update");
231234
}

‎telegrambots-abilities/src/test/java/org/telegram/telegrambots/abilitybots/api/objects/FlagTest.java‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
import org.telegram.telegrambots.meta.api.objects.boost.ChatBoostUpdated;
88
import org.telegram.telegrambots.meta.api.objects.gifts.Gift;
99
import org.telegram.telegrambots.meta.api.objects.gifts.GiftInfo;
10+
import org.telegram.telegrambots.meta.api.objects.chat.Chat;
1011
import org.telegram.telegrambots.meta.api.objects.message.Message;
12+
import org.telegram.telegrambots.meta.api.objects.message.MessageGenerationStopped;
1113
import org.telegram.telegrambots.meta.api.objects.payments.BotSubscriptionUpdated;
1214
import org.telegram.telegrambots.meta.api.objects.reactions.MessageReactionCountUpdated;
1315
import org.telegram.telegrambots.meta.api.objects.reactions.MessageReactionUpdated;
@@ -72,4 +74,19 @@ void testSubscriptionFlag() {
7274
void testSubscriptionFlagIsFalseWithoutSubscription() {
7375
assertFalse(Flag.HAS_SUBSCRIPTION.test(new Update()));
7476
}
77+
78+
@Test
79+
void testStoppedMessageGenerationFlag() {
80+
Update update = new Update();
81+
update.setStoppedMessageGeneration(MessageGenerationStopped.builder()
82+
.chat(Chat.builder().id(456L).type("private").build())
83+
.draftId(7)
84+
.build());
85+
assertTrue(Flag.STOPPED_MESSAGE_GENERATION.test(update));
86+
}
87+
88+
@Test
89+
void testStoppedMessageGenerationFlagIsFalseWithoutIt() {
90+
assertFalse(Flag.STOPPED_MESSAGE_GENERATION.test(new Update()));
91+
}
7592
}

‎telegrambots-abilities/src/test/java/org/telegram/telegrambots/abilitybots/api/util/AbilityUtilsTest.java‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.telegram.telegrambots.meta.api.objects.boost.ChatBoostUpdated;
99
import org.telegram.telegrambots.meta.api.objects.chat.Chat;
1010
import org.telegram.telegrambots.meta.api.objects.message.Message;
11+
import org.telegram.telegrambots.meta.api.objects.message.MessageGenerationStopped;
1112
import org.telegram.telegrambots.meta.api.objects.payments.BotSubscriptionUpdated;
1213
import org.telegram.telegrambots.meta.api.objects.reactions.MessageReactionCountUpdated;
1314
import org.telegram.telegrambots.meta.api.objects.reactions.MessageReactionUpdated;
@@ -117,4 +118,28 @@ void testGetChatIdFromSubscription() {
117118
.build());
118119
assertEquals(123L, AbilityUtils.getChatId(update));
119120
}
121+
122+
@Test
123+
void testGetChatIdFromStoppedMessageGeneration() {
124+
Update update = new Update();
125+
update.setStoppedMessageGeneration(MessageGenerationStopped.builder()
126+
.chat(Chat.builder().id(456L).type("private").build())
127+
.draftId(7)
128+
.build());
129+
assertEquals(456L, AbilityUtils.getChatId(update));
130+
}
131+
132+
/**
133+
* A stopped-generation update carries no originating user, so getUser must fall through to
134+
* EMPTY_USER rather than throwing IllegalStateException.
135+
*/
136+
@Test
137+
void testGetUserFromStoppedMessageGenerationIsEmptyUser() {
138+
Update update = new Update();
139+
update.setStoppedMessageGeneration(MessageGenerationStopped.builder()
140+
.chat(Chat.builder().id(456L).type("private").build())
141+
.draftId(7)
142+
.build());
143+
assertEquals(AbilityUtils.EMPTY_USER, AbilityUtils.getUser(update));
144+
}
120145
}

‎telegrambots-bom/pom.xml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<parent>
88
<groupId>org.telegram</groupId>
99
<artifactId>Bots</artifactId>
10-
<version>10.2.1</version>
10+
<version>10.3.0</version>
1111
</parent>
1212

1313
<artifactId>telegrambots-bom</artifactId>

‎telegrambots-client-jetty-adapter/pom.xml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<parent>
88
<groupId>org.telegram</groupId>
99
<artifactId>Bots</artifactId>
10-
<version>10.2.1</version>
10+
<version>10.3.0</version>
1111
</parent>
1212

1313
<name>Telegram Bots Client Jetty HttpClient adapter</name>

‎telegrambots-client-jetty-adapter/src/main/java/org/telegram/telegrambots/client/AbstractTelegramClient.java‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.telegram.telegrambots.meta.api.methods.stickers.SetStickerSetThumbnail;
2121
import org.telegram.telegrambots.meta.api.methods.stickers.UploadStickerFile;
2222
import org.telegram.telegrambots.meta.api.methods.updates.SetWebhook;
23+
import org.telegram.telegrambots.meta.api.methods.updatingmessages.EditEphemeralMessageMedia;
2324
import org.telegram.telegrambots.meta.api.methods.updatingmessages.EditMessageMedia;
2425
import org.telegram.telegrambots.meta.api.objects.File;
2526
import org.telegram.telegrambots.meta.api.objects.message.Message;
@@ -215,6 +216,15 @@ public Serializable execute(EditMessageMedia editMessageMedia) throws TelegramAp
215216
}
216217
}
217218

219+
@Override
220+
public Boolean execute(EditEphemeralMessageMedia editEphemeralMessageMedia) throws TelegramApiException {
221+
try {
222+
return executeAsync(editEphemeralMessageMedia).get();
223+
} catch (Exception e) {
224+
throw mapException(e, editEphemeralMessageMedia.getMethod());
225+
}
226+
}
227+
218228
@Override
219229
public Message execute(SendAnimation sendAnimation) throws TelegramApiException {
220230
try {

‎telegrambots-client-jetty-adapter/src/main/java/org/telegram/telegrambots/client/jetty/JettyTelegramClient.java‎

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.telegram.telegrambots.meta.api.methods.stickers.SetStickerSetThumbnail;
3636
import org.telegram.telegrambots.meta.api.methods.stickers.UploadStickerFile;
3737
import org.telegram.telegrambots.meta.api.methods.updates.SetWebhook;
38+
import org.telegram.telegrambots.meta.api.methods.updatingmessages.EditEphemeralMessageMedia;
3839
import org.telegram.telegrambots.meta.api.methods.updatingmessages.EditMessageMedia;
3940
import org.telegram.telegrambots.meta.api.objects.File;
4041
import org.telegram.telegrambots.meta.api.objects.InputFile;
@@ -632,6 +633,32 @@ public CompletableFuture<Serializable> executeAsync(EditMessageMedia editMessage
632633
}
633634
}
634635

636+
@Override
637+
public CompletableFuture<Boolean> executeAsync(EditEphemeralMessageMedia editEphemeralMessageMedia) {
638+
try {
639+
assertParamNotNull(editEphemeralMessageMedia, "editEphemeralMessageMedia");
640+
editEphemeralMessageMedia.validate();
641+
642+
URI url = buildUrl(editEphemeralMessageMedia.getMethod());
643+
644+
JettyMultipartBuilder builder = new JettyMultipartBuilder(objectMapper);
645+
646+
builder.addPart(EditEphemeralMessageMedia.CHAT_ID_FIELD, editEphemeralMessageMedia.getChatId())
647+
.addPart(EditEphemeralMessageMedia.RECEIVER_USER_ID_FIELD, editEphemeralMessageMedia.getReceiverUserId())
648+
.addPart(EditEphemeralMessageMedia.EPHEMERAL_MESSAGE_ID_FIELD, editEphemeralMessageMedia.getEphemeralMessageId())
649+
.addJsonPart(EditEphemeralMessageMedia.REPLY_MARKUP_FIELD, editEphemeralMessageMedia.getReplyMarkup());
650+
651+
addInputData(builder, EditEphemeralMessageMedia.MEDIA_FIELD, editEphemeralMessageMedia.getMedia(), true);
652+
653+
Request httpPost = client.POST(url).body(builder.build());
654+
return sendRequest(editEphemeralMessageMedia, httpPost);
655+
} catch (TelegramApiException e) {
656+
return CompletableFuture.failedFuture(e);
657+
} catch (IOException e) {
658+
return CompletableFuture.failedFuture(new TelegramApiException("Unable to execute " + editEphemeralMessageMedia.getMethod(), e));
659+
}
660+
}
661+
635662
@Override
636663
public CompletableFuture<java.io.File> downloadFileAsync(File file) {
637664
return downloadFileAsStreamAsync(file).thenApply(stream -> {
@@ -712,7 +739,8 @@ private <T extends Serializable, Method extends SendMediaBotMethod<T>> Completab
712739
.addPart(SendMediaBotMethod.PROTECT_CONTENT_FIELD, method.getProtectContent())
713740
.addPart(SendMediaBotMethod.MESSAGE_EFFECT_ID_FIELD, method.getMessageEffectId())
714741
.addPart(SendMediaBotMethod.DIRECT_MESSAGES_TOPIC_ID_FIELD, method.getDirectMessagesTopicId())
715-
.addPart(SendMediaBotMethod.SUGGESTED_POST_PARAMETERS_FIELD, method.getSuggestedPostParameters())
742+
.addJsonPart(SendMediaBotMethod.SUGGESTED_POST_PARAMETERS_FIELD, method.getSuggestedPostParameters())
743+
.addJsonPart(SendMediaBotMethod.EPHEMERAL_MESSAGE_PARAMETERS_FIELD, method.getEphemeralMessageParameters())
716744
.addJsonPart(SendMediaBotMethod.REPLY_PARAMETERS_FIELD, method.getReplyParameters())
717745
.addJsonPart(SendMediaBotMethod.REPLY_MARKUP_FIELD, method.getReplyMarkup());
718746

0 commit comments

Comments
 (0)