mirror of
https://github.com/InsanusMokrassar/TelegramBotAPI-examples.git
synced 2026-09-25 18:05:29 +00:00
update examples for ktgbotapi 37.0.0
This commit is contained in:
@@ -2,7 +2,8 @@
|
||||
|
||||
DraftsBot demonstrates streaming a message draft before sending the finished
|
||||
message. It receives updates through long polling and uses the same built-in
|
||||
Lorem ipsum text for both examples.
|
||||
Lorem ipsum text for all examples. Bot API 10.3's stoppable generation controls
|
||||
and `stopped_message_generation` updates are included.
|
||||
|
||||
## Commands
|
||||
|
||||
@@ -10,18 +11,32 @@ Lorem ipsum text for both examples.
|
||||
500 ms, then sends the complete text as a normal message.
|
||||
- `/test_empty_draft` first publishes an empty draft, waits 1.5 seconds, streams
|
||||
the same prefixes, and then sends the complete text.
|
||||
- `/test_stoppable_draft` continuously streams progressively longer revisions
|
||||
with `canStop = true` and `keepOnStop = true`. Telegram displays a stop control;
|
||||
stopping keeps the most recent draft revision and causes the flow helper to
|
||||
return `false`. Before streaming, the handler subscribes to
|
||||
`waitMessageGenerationStopped` and filters the expectation by chat and draft
|
||||
ID, then logs the matched stop event. It deliberately sends no confirmation
|
||||
message, because sending one would immediately remove the draft retained by
|
||||
`keepOnStop`. If streaming ends because of another request failure and no
|
||||
matching update arrives within five seconds, the bot logs that distinction
|
||||
instead of waiting forever.
|
||||
|
||||
The bot advertises both commands in Telegram's command menu for all group chats.
|
||||
The handlers themselves are not restricted by chat type, so either command can
|
||||
also be entered manually in a private chat.
|
||||
The bot advertises all three commands in Telegram's command menu for private
|
||||
chats and filters each handler to private chats, as required by Telegram's draft
|
||||
methods.
|
||||
|
||||
The typed `onMessageGenerationStopped` handler logs the chat, optional topic ID,
|
||||
and draft ID from every generation-stopped update, independently of the scoped
|
||||
expectation used by `/test_stoppable_draft`. Both apply to text and rich-message
|
||||
drafts sent by this bot token.
|
||||
|
||||
## Setup
|
||||
|
||||
1. Obtain a bot token and keep it out of source control.
|
||||
2. Start a private chat with the bot, or add it to a group where you want to run
|
||||
the example.
|
||||
3. In groups, allow the bot to send messages. No administrator rights are
|
||||
otherwise required by this example.
|
||||
2. Start a private chat with the bot. Telegram doesn't accept a group or channel
|
||||
ID for `sendMessageDraft`.
|
||||
3. No administrator rights are required by this example.
|
||||
|
||||
The first program argument is required and must be the bot token. Omitting it
|
||||
causes startup to fail; any later arguments are ignored.
|
||||
@@ -34,8 +49,6 @@ From the repository root, run:
|
||||
./gradlew :DraftsBot:run --args="<BOT_TOKEN>"
|
||||
```
|
||||
|
||||
> **Known issue:** `DraftsBot/build.gradle` currently declares `TopicsHandlingKt` as the main class, while this bot's entry point is `DraftsBotKt`. The `run` task cannot start until that Gradle setting is corrected; it is left unchanged by this documentation-only update.
|
||||
|
||||
Every received update is printed to standard output. Unhandled polling errors
|
||||
are printed with their stack traces, and HTTP request, socket, and connection
|
||||
timeouts are each configured to 30 seconds.
|
||||
|
||||
@@ -11,7 +11,7 @@ buildscript {
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'application'
|
||||
|
||||
mainClassName="TopicsHandlingKt"
|
||||
mainClassName="DraftsBotKt"
|
||||
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -12,7 +12,9 @@ import dev.inmo.tgbotapi.extensions.api.send.send
|
||||
import dev.inmo.tgbotapi.extensions.api.send.sendMessageDraftFlow
|
||||
import dev.inmo.tgbotapi.extensions.api.send.sendMessageDraftFlowWithTexts
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitMessageGenerationStopped
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMessageGenerationStopped
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onForumTopicClosed
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onForumTopicCreated
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onForumTopicEdited
|
||||
@@ -30,18 +32,26 @@ import dev.inmo.tgbotapi.types.BotCommand
|
||||
import dev.inmo.tgbotapi.types.ForumTopic
|
||||
import dev.inmo.tgbotapi.types.chat.PrivateChat
|
||||
import dev.inmo.tgbotapi.types.commands.BotCommandScope
|
||||
import dev.inmo.tgbotapi.utils.DraftIdAllocator
|
||||
import io.ktor.client.plugins.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
|
||||
/** Sample text streamed as a draft and then sent as the completed message. */
|
||||
const val testText = """
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
"""
|
||||
|
||||
private val stoppableDraftIds = DraftIdAllocator()
|
||||
|
||||
/**
|
||||
* Starts DraftsBot with long polling and registers the draft demonstration commands.
|
||||
*
|
||||
@@ -67,7 +77,7 @@ suspend fun main(vararg args: String) {
|
||||
}
|
||||
}
|
||||
) {
|
||||
onCommand("test_draft_flow") {
|
||||
onCommand("test_draft_flow", initialFilter = { it.chat is PrivateChat }) {
|
||||
sendMessageDraftFlowWithTexts(
|
||||
it.chat.id,
|
||||
flow<String> {
|
||||
@@ -85,7 +95,7 @@ suspend fun main(vararg args: String) {
|
||||
|
||||
// sendMessageDraft now accepts empty text (length 0 is valid since TG Bot API 9.0)
|
||||
// Useful to show a typing indicator without any text yet
|
||||
onCommand("test_empty_draft") {
|
||||
onCommand("test_empty_draft", initialFilter = { it.chat is PrivateChat }) {
|
||||
sendMessageDraftFlowWithTexts(
|
||||
it.chat.id,
|
||||
flow<String> {
|
||||
@@ -103,10 +113,62 @@ suspend fun main(vararg args: String) {
|
||||
send(it.chat, testText)
|
||||
}
|
||||
|
||||
// Bot API 10.3 lets the user stop generation. The expectation is subscribed before streaming starts so it
|
||||
// cannot miss a fast stop update; matching both chat and draft ID also avoids consuming another draft's event.
|
||||
onCommand("test_stoppable_draft", initialFilter = { it.chat is PrivateChat }) { origin ->
|
||||
val draftId = stoppableDraftIds.allocate()
|
||||
val stoppedUpdate = async(start = CoroutineStart.UNDISPATCHED) {
|
||||
waitMessageGenerationStopped()
|
||||
.filter { it.chat.id == origin.chat.id && it.draftId == draftId }
|
||||
.first()
|
||||
}
|
||||
try {
|
||||
val completed = sendMessageDraftFlowWithTexts(
|
||||
origin.chat.id,
|
||||
flow<String> {
|
||||
val step = 20
|
||||
var currentLength = step
|
||||
while (isActive) {
|
||||
delay(500L)
|
||||
emit(testText.take(currentLength.coerceAtMost(testText.length)))
|
||||
currentLength = (currentLength + step).coerceAtMost(testText.length)
|
||||
}
|
||||
},
|
||||
draftId = draftId,
|
||||
canStop = true,
|
||||
keepOnStop = true,
|
||||
)
|
||||
if (!completed) {
|
||||
val stopped = withTimeoutOrNull(5_000L) { stoppedUpdate.await() }
|
||||
if (stopped == null) {
|
||||
println("Draft streaming ended without a matching stopped_message_generation update")
|
||||
} else {
|
||||
// A normal message here would immediately remove the kept draft, so report only to stdout.
|
||||
println(
|
||||
"Expectation matched stopped draft ${stopped.draftId.long} in ${stopped.chat.id}; " +
|
||||
"Telegram kept its last revision temporarily."
|
||||
)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
stoppedUpdate.cancel()
|
||||
stoppableDraftIds.free(draftId)
|
||||
}
|
||||
}
|
||||
|
||||
// The stopped_message_generation update includes the chat, optional topic and stopped draft ID.
|
||||
onMessageGenerationStopped { stopped ->
|
||||
println(
|
||||
"Message generation stopped in ${stopped.chat.id}; " +
|
||||
"thread=${stopped.messageThreadId}, draft=${stopped.draftId}"
|
||||
)
|
||||
}
|
||||
|
||||
setMyCommands(
|
||||
BotCommand("test_draft_flow", "Start draft testing with flow"),
|
||||
BotCommand("test_empty_draft", "Draft starting from empty text (TG Bot API 9.0)"),
|
||||
scope = BotCommandScope.AllGroupChats
|
||||
BotCommand("test_stoppable_draft", "Stream a draft that the user can stop"),
|
||||
scope = BotCommandScope.AllPrivateChats
|
||||
)
|
||||
allUpdatesFlow.subscribeLoggingDropExceptions(this) {
|
||||
println(it)
|
||||
|
||||
Reference in New Issue
Block a user