update examples for ktgbotapi 37.0.0

This commit is contained in:
2026-08-30 17:44:15 +06:00
parent ce48393895
commit 447de0c3ce
19 changed files with 603 additions and 138 deletions

View File

@@ -1,22 +1,31 @@
# EphemeralMessagesBot
Demonstrates ephemeral messages: group messages that Telegram shows only to one receiver.
Demonstrates Telegram Bot API 10.2 and 10.3 ephemeral messages: group messages that Telegram shows only to one receiver.
## Behavior
- `/ephemeral` replies with a **Reveal a secret** inline button. The command is registered with Telegram as
an ephemeral command.
- Pressing the button (`reveal` callback data) sends a personal message visible only to the user who
pressed it. After three seconds the bot edits the message, then deletes it three seconds later.
- Pressing the button (`reveal` callback data) uses `EphemeralMessageParameters` to replace the callback-query
message with a personal rich message visible only to the user who pressed it. After three seconds the bot replaces
its text with typed rich blocks, then deletes it three seconds later.
- `/ephemeral_photo` waits for a photo from the same user and chat, sends it back ephemerally by its Telegram file ID,
downloads it, and edits the ephemeral media using a new multipart upload. A second edit sets
`showCaptionAboveMedia = true`.
- `/ephemeral_live_photo` waits for a Live Photo, sends it ephemerally by existing file IDs, then downloads and
re-uploads both its main file and secondary `photo` file in one `editEphemeralMessageMedia` request. This exercises
ktgbotapi 37.0.0's secondary multipart attachment collection.
- When the bot receives an ephemeral content message, it sends two ephemeral replies: one through the
general `reply` API and one through the explicit `replyToEphemeral` API.
general `reply` API and one through the explicit `replyToEphemeral` API. The explicit form also uses
`EphemeralMessageParameters`.
- Updates and basic bot information are printed to standard output.
## Setup
Create a bot token, keep it secret, and add the bot to a group. The bot must be allowed to send messages
there; this example does not request or validate group permissions itself. Use a Telegram environment that
supports ephemeral messages.
supports ephemeral messages. The photo demonstrations download all selected media into memory before uploading it
again.
## Run

View File

@@ -6,29 +6,47 @@ import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands
import dev.inmo.tgbotapi.extensions.api.deleteEphemeralMessage
import dev.inmo.tgbotapi.extensions.api.edit.text.editEphemeralMessageText
import dev.inmo.tgbotapi.extensions.api.edit.caption.editEphemeralMessageCaption
import dev.inmo.tgbotapi.extensions.api.edit.media.editEphemeralMessageMedia
import dev.inmo.tgbotapi.extensions.api.edit.text.editEphemeralMessageRichText
import dev.inmo.tgbotapi.extensions.api.files.downloadFile
import dev.inmo.tgbotapi.extensions.api.send.reply
import dev.inmo.tgbotapi.extensions.api.send.replyToEphemeral
import dev.inmo.tgbotapi.extensions.api.send.sendTextMessage
import dev.inmo.tgbotapi.extensions.api.send.sendRichMessage
import dev.inmo.tgbotapi.extensions.api.send.media.sendLivePhoto
import dev.inmo.tgbotapi.extensions.api.send.media.sendPhoto
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitLivePhotoMessage
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitPhotoMessage
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onContentMessage
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMessageDataCallbackQuery
import dev.inmo.tgbotapi.extensions.utils.fromUserMessageOrNull
import dev.inmo.tgbotapi.extensions.utils.extensions.sameChat
import dev.inmo.tgbotapi.extensions.utils.types.buttons.dataButton
import dev.inmo.tgbotapi.extensions.utils.types.buttons.flatInlineKeyboard
import dev.inmo.tgbotapi.requests.abstracts.asMultipartFile
import dev.inmo.tgbotapi.types.BotCommand
import dev.inmo.tgbotapi.types.EphemeralMessageParameters
import dev.inmo.tgbotapi.types.ephemeralReplyReceiverUserIdOrNull
import dev.inmo.tgbotapi.types.media.TelegramMediaLivePhoto
import dev.inmo.tgbotapi.types.media.TelegramMediaPhoto
import dev.inmo.tgbotapi.types.message.abstracts.PossiblyEphemeralMessage
import dev.inmo.tgbotapi.types.rich.InputRichMessageBlocks
import korlibs.time.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
/**
* Runs the ephemeral-messages example bot using long polling.
*
* `/ephemeral` posts an inline button whose callback sends, edits, and deletes a message visible only to
* the user who pressed it. Incoming [PossiblyEphemeralMessage] instances receive both an automatic
* `/ephemeral` posts an inline button whose callback replaces it with a rich message visible only to
* the user who pressed it, edits that message, and deletes it. `/ephemeral_photo` demonstrates uploading new media
* to an ephemeral edit and moving its caption above the media. `/ephemeral_live_photo` demonstrates collecting both
* multipart files of a Live Photo edit. Incoming [PossiblyEphemeralMessage] instances receive both an automatic
* ephemeral [reply] and an explicit [replyToEphemeral].
*
* [args] must start with the bot token. The optional exact values `debug` and `testServer` respectively
@@ -68,18 +86,25 @@ suspend fun main(vararg args: String) {
)
}
// Send an ephemeral message in response to a callback query. `receiverUserId` + `callbackQueryId`
// make the outgoing message ephemeral — Telegram shows it only to the querying user (and this also
// serves as the answer to the callback query).
// Bot API 10.3 groups the recipient/callback fields in EphemeralMessageParameters. Setting
// replaceCallbackQueryMessage replaces the button message only for the user who pressed it.
onMessageDataCallbackQuery(Regex("reveal")) { query ->
val chatId = query.message.chat.id
val receiverUserId = query.from.id
val sent = sendTextMessage(
val sent = sendRichMessage(
chatId,
"🔒 ${query.from.firstName}, here is your personal secret: 42",
receiverUserId = receiverUserId,
callbackQueryId = query.id,
InputRichMessageBlocks {
paragraph {
plain("🔒 ${query.from.firstName}, here is your personal secret: ")
code("42")
}
},
ephemeralMessageParameters = EphemeralMessageParameters(
receiverUserId = receiverUserId,
callbackQueryId = query.id,
replaceCallbackQueryMessage = true,
),
)
// Only the group-family Common*ContentMessage types implement PossiblyEphemeralMessage, so the
@@ -87,14 +112,91 @@ suspend fun main(vararg args: String) {
val ephemeralMessageId = (sent as? PossiblyEphemeralMessage)?.ephemeralMessageId
if (ephemeralMessageId != null) {
delay(3.seconds)
// editEphemeralMessageText: address the ephemeral message by chatId + receiverUserId + ephemeralMessageId
editEphemeralMessageText(chatId, receiverUserId, ephemeralMessageId, "🔓 Revealed: the answer is 42")
// editEphemeralMessageText now accepts rich_message; the typed extension exposes that as
// editEphemeralMessageRichText.
editEphemeralMessageRichText(
chatId,
receiverUserId,
ephemeralMessageId,
InputRichMessageBlocks {
h2("Secret revealed")
paragraph { plain("The answer is "); code("42") }
},
)
delay(3.seconds)
// deleteEphemeralMessage: same addressing (there is also a PossiblyEphemeralMessage overload)
deleteEphemeralMessage(chatId, receiverUserId, ephemeralMessageId)
}
}
// Upload a received photo again as a brand-new multipart file while editing an ephemeral media message.
onCommand("ephemeral_photo") { origin ->
val receiverUserId = origin.fromUserMessageOrNull()?.user?.id ?: return@onCommand
reply(origin, "Send a photo. I will return it as ephemeral media and then re-upload it in an edit.")
val photoMessage = waitPhotoMessage().filter {
it.sameChat(origin) && it.fromUserMessageOrNull()?.user?.id == receiverUserId
}.first()
val sent = sendPhoto(
photoMessage.chat.id,
photoMessage.content.media.fileId,
text = "Ephemeral photo using its existing Telegram file ID",
ephemeralMessageParameters = EphemeralMessageParameters(receiverUserId),
)
val ephemeralMessageId = (sent as? PossiblyEphemeralMessage)?.ephemeralMessageId
?: return@onCommand
val photoBytes = downloadFile(photoMessage.content)
editEphemeralMessageMedia(
photoMessage.chat.id,
receiverUserId,
ephemeralMessageId,
TelegramMediaPhoto(photoBytes.asMultipartFile("ephemeral-photo.jpg")),
)
editEphemeralMessageCaption(
photoMessage.chat.id,
receiverUserId,
ephemeralMessageId,
caption = "This caption is above newly uploaded media",
showCaptionAboveMedia = true,
)
}
// A Live Photo has a main file plus a secondary `photo` file. ktgbotapi 37.0.0 includes both multipart
// attachments when EditEphemeralMessageMedia builds its request.
onCommand("ephemeral_live_photo") { origin ->
val receiverUserId = origin.fromUserMessageOrNull()?.user?.id ?: return@onCommand
reply(origin, "Send a Live Photo. I will send it ephemerally and edit it using two new uploads.")
val livePhotoMessage = waitLivePhotoMessage().filter {
it.sameChat(origin) && it.fromUserMessageOrNull()?.user?.id == receiverUserId
}.first()
val livePhoto = livePhotoMessage.content.media
val sent = sendLivePhoto(
chatId = livePhotoMessage.chat.id,
livePhoto = livePhoto,
text = "Ephemeral Live Photo using existing Telegram file IDs",
ephemeralMessageParameters = EphemeralMessageParameters(receiverUserId),
)
val ephemeralMessageId = (sent as? PossiblyEphemeralMessage)?.ephemeralMessageId
?: return@onCommand
val livePhotoBytes = downloadFile(livePhoto)
val coverPhotoBytes = livePhoto.photo?.let { downloadFile(it) }
editEphemeralMessageMedia(
livePhotoMessage.chat.id,
receiverUserId,
ephemeralMessageId,
TelegramMediaLivePhoto(
file = livePhotoBytes.asMultipartFile("ephemeral-live-photo.mp4"),
photo = coverPhotoBytes?.asMultipartFile("ephemeral-live-photo-cover.jpg")
?: livePhoto.photo?.fileId
?: livePhoto.fileId,
text = "Edited with newly uploaded main and cover files",
),
)
}
// Incoming ephemeral messages: detect them via PossiblyEphemeralMessage, then answer them.
onContentMessage { message ->
val ephemeral = (message as? PossiblyEphemeralMessage)?.takeIf { it.ephemeralMessageId != null }
@@ -109,7 +211,7 @@ suspend fun main(vararg args: String) {
if (receiverUserId != null) {
replyToEphemeral(
message.chat.id,
receiverUserId,
EphemeralMessageParameters(receiverUserId),
ephemeral.ephemeralMessageId!!,
"Explicit ephemeral reply via replyToEphemeral",
)
@@ -119,6 +221,8 @@ suspend fun main(vararg args: String) {
setMyCommands(
// isEphemeral marks a command whose response is an ephemeral (personal) message
BotCommand("ephemeral", "Post a button that reveals an ephemeral (personal) message", isEphemeral = true),
BotCommand("ephemeral_photo", "Re-upload a photo through an ephemeral media edit", isEphemeral = true),
BotCommand("ephemeral_live_photo", "Re-upload both files of an ephemeral Live Photo", isEphemeral = true),
)
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {