smithery/andvl1

ktgbotapi

KTgBotAPI 33.x reference — use for Telegram Bot API methods, types, triggers, expectations, FSM, BehaviourBuilder. Always pin to 33.1.0; do not regress to 31.x or 32.x even if your training data is older — the API surface is incompatible.

Installation

$ npx skills add smithery/andvl1 --skill ktgbotapi

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery/andvl1.

npx skills add smithery/andvl1

Browse all from smithery/andvl1

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 14,683 B
  • docs SUMMARY.md 124 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

KTgBotAPI Reference

Kotlin Multiplatform library for Telegram Bot API. Type-safe, coroutine-based.

Setup

// build.gradle.kts
dependencies {
    implementation("dev.inmo:tgbotapi:33.1.0")
}

v32 → v33 breaking changes (read before writing code)

If your training data is from before mid-2025, these traps apply:

  • BotToken is now a value class. Cannot pass a raw String token everywhere — wrap with BotToken(System.getenv("BOT_TOKEN")) when an API expects it. telegramBot("...") still accepts a String for the helper.
  • Many bot-action methods return Unit, not Boolean. E.g. setMyCommands, deleteMessages, pinChatMessage. Do not if (bot.deleteMessage(...)) — it does not compile.
  • MultipleAnswersPoll removed. Use RegularPoll with allowsMultipleAnswers: Boolean.
  • correctOptionId: Int? → correctOptionIds: List<Int> on quiz polls.
  • **InputMedia* constructors reorganized** — accept MediaContent directly; some optional positions shifted.
  • Various expectations package reshuffles — prefer waitText { ... }, waitDataCallbackQuery { ... } from expectations.*.

Required Kotlin / coroutines floor

  • Kotlin 2.1+ (2.3.21 recommended).
  • kotlinx-coroutines-core 1.10+.

Modules

Module Purpose
tgbotapi.core Core types, requests
tgbotapi.api API extension methods
tgbotapi.utils Utilities, keyboard builders
tgbotapi.behaviour_builder BehaviourBuilder DSL
tgbotapi.behaviour_builder.fsm FSM integration

Quick Start

suspend fun main() {
    val bot = telegramBot(System.getenv("BOT_TOKEN"))

    bot.buildBehaviourWithLongPolling {
        onCommand("start") { reply(it, "Hello!") }
    }.join()
}

Triggers Reference

Commands

CRITICAL: Understanding requireOnlyCommandInMessage parameter

By default, onCommand has requireOnlyCommandInMessage = true, meaning it ONLY triggers when the message contains JUST the command with no additional text.

// DEFAULT BEHAVIOR - triggers ONLY for "/start" (no extra text)
onCommand("start") { message -> }

// This will NOT trigger for "/start hello" or "/warn @username"!

For commands with arguments, you MUST use one of these approaches:

// APPROACH 1: Set requireOnlyCommandInMessage = false
// Triggers for "/mute @username reason" - you parse args manually
onCommand("mute", requireOnlyCommandInMessage = false) { message ->
    val text = (message.content as TextContent).text
    val args = text.split(" ").drop(1)  // skip command
}

// APPROACH 2: Use onCommandWithArgs (recommended for simple args)
// Automatically sets requireOnlyCommandInMessage = false and parses args
onCommandWithArgs("echo") { message, args ->
    // args = arrayOf("hello", "world") for "/echo hello world"
    reply(message, args.joinToString(" "))
}

// APPROACH 3: Use onCommandWithNamedArgs for key=value format
onCommandWithNamedArgs("config") { message, args ->
    // args = listOf("key" to "value") for "/config key=value"
}

Common patterns:

onCommand("start") { message -> }                          // no args expected
onCommand("help", "info") { message -> }                   // multiple commands, no args
onCommand(Regex("set_.*")) { message -> }                  // regex pattern
onCommand("warn", requireOnlyCommandInMessage = false) { } // manual arg parsing
onCommandWithArgs("echo") { message, args -> }             // auto arg parsing
onDeepLink { message, deepLink -> }                        // t.me/bot?start=payload
onUnhandledCommand { message -> }                          // fallback for unknown commands

Text

onText { message -> }
onText(initialFilter = { it.content.text.length > 10 }) { message -> }
onText(Regex("\\d+")) { message -> }

Media

onPhoto { message -> }
onVideo { message -> }
onAudio { message -> }
onDocument { message -> }
onVoice { message -> }
onVideoNote { message -> }
onSticker { message -> }
onAnimation { message -> }
onMediaGroup { messages -> }                       // album
onVisualMediaGroup { messages -> }                 // photos/videos only

Callbacks & Queries

onDataCallbackQuery { query -> }
onDataCallbackQuery(Regex("action:.*")) { query -> }
onInlineQuery { query -> }
onChosenInlineResult { result -> }

Other Updates

onContact { message -> }
onLocation { message -> }
onPoll { poll -> }
onPollAnswer { answer -> }
onChatMemberUpdated { update -> }
onMyChatMemberUpdated { update -> }
onNewChatMembers { message -> }
onLeftChatMember { message -> }

Expectations Reference

Wait for specific user input:

// Wait for text
val text = waitText().first()
val text = waitText { it.chat.id == chatId }.first()

// Wait for media
val photo = waitPhoto().first()
val document = waitDocument().first()

// Wait for callback
val callback = waitDataCallbackQuery().first()
val callback = waitDataCallbackQuery { it.data.startsWith("confirm:") }.first()

// With request (send message and wait)
val photo = waitPhoto(
    SendTextMessage(chatId, "Send me a photo")
).first()

Sending Messages

// Text
sendMessage(chatId, "Hello")
sendTextMessage(chatId, "Hello", parseMode = ParseMode.HTML)
reply(message, "Reply text")

// With entities
send(chatId, buildEntities {
    bold("Bold") + " and " + italic("italic")
})

// Media
sendPhoto(chatId, InputFile.fromFile(File("photo.jpg")))
sendPhoto(chatId, InputFile.fromUrl("https://..."))
sendDocument(chatId, InputFile.fromFile(File("doc.pdf")))
sendVideo(chatId, InputFile.fromFile(File("video.mp4")))
sendAudio(chatId, InputFile.fromFile(File("audio.mp3")))
sendVoice(chatId, InputFile.fromFile(File("voice.ogg")))
sendSticker(chatId, InputFile.fromId(stickerFileId))

// Media group
sendMediaGroup(chatId, listOf(
    TelegramMediaPhoto(InputFile.fromFile(File("1.jpg"))),
    TelegramMediaPhoto(InputFile.fromFile(File("2.jpg")))
))

// Location
sendLocation(chatId, latitude = 55.75, longitude = 37.62)

// Contact
sendContact(chatId, phoneNumber = "+123456789", firstName = "John")

Text Formatting

buildEntities DSL

val text = buildEntities {
    bold("Bold") + "\n"
    italic("Italic") + "\n"
    underline("Underline") + "\n"
    strikethrough("Strike") + "\n"
    spoiler("Spoiler") + "\n"
    code("inline code") + "\n"
    pre("code block", language = "kotlin")
    link("Link", "https://example.com") + "\n"
    mention("username")
    textMention("User", userId)
    botCommand("start")
    hashtag("tag")
    cashtag("USD")
    email("[email protected]")
    phoneNumber("+123456789")
    regular("Plain text")
}

Parse Modes

// HTML
sendTextMessage(chatId, """
    <b>Bold</b>, <i>italic</i>, <u>underline</u>
    <s>strikethrough</s>, <tg-spoiler>spoiler</tg-spoiler>
    <code>code</code>, <pre>block</pre>
    <a href="https://...">link</a>
""".trimIndent(), parseMode = ParseMode.HTML)

// MarkdownV2 (escape special chars: _*[]()~`>#+-=|{}.!)
sendTextMessage(chatId, """
    *bold*, _italic_, __underline__
    ~strikethrough~, ||spoiler||
    `code`, ```block```
    [link](https://...)
""".trimIndent(), parseMode = ParseMode.MarkdownV2)

Reply Keyboard

val keyboard = replyKeyboard(
    resizeKeyboard = true,
    oneTimeKeyboard = true,
    inputFieldPlaceholder = "Choose option"
) {
    row {
        simpleButton("Button 1")
        simpleButton("Button 2")
    }
    row {
        requestContactButton("Share Contact")
        requestLocationButton("Share Location")
    }
    row {
        requestPollButton("Create Poll", type = RegularPoll)
        webAppButton("Web App", WebAppInfo("https://..."))
    }
}

sendMessage(chatId, "Menu:", replyMarkup = keyboard)

// Remove keyboard
sendMessage(chatId, "Done", replyMarkup = ReplyKeyboardRemove())

Inline Keyboard

val keyboard = inlineKeyboard {
    row {
        dataButton("Action", "callback_data")
        urlButton("Link", "https://example.com")
    }
    row {
        webAppButton("Web App", WebAppInfo("https://..."))
        loginButton("Login", LoginUrl("https://..."))
    }
    row {
        inlineQueryInCurrentChatButton("Search", "query")
        inlineQueryInChosenChatButton("Search chat", "query")
    }
    row {
        payButton("Pay")
        gameButton("Play")
    }
}

sendMessage(chatId, "Actions:", replyMarkup = keyboard)

Callback Queries

onDataCallbackQuery { query ->
    val data = query.data        // callback data string
    val message = query.message  // original message (nullable)
    val user = query.user        // user who clicked

    // Answer callback (removes loading indicator)
    answer(query)
    answer(query, "Notification text")
    answer(query, "Alert!", showAlert = true)

    // Edit original message
    edit(query.message!!, "New text")
    edit(query.message!!, replyMarkup = newKeyboard)

    // Delete original message
    delete(query.message!!)
}

Inline Mode

onInlineQuery { query ->
    val searchQuery = query.query

    val results = listOf(
        InlineQueryResultArticle(
            id = "1",
            title = "Result Title",
            description = "Description",
            inputMessageContent = InputTextMessageContent("Selected: Result 1"),
            replyMarkup = inlineKeyboard { row { dataButton("Details", "d:1") } }
        ),
        InlineQueryResultPhoto(
            id = "2",
            photoUrl = "https://example.com/photo.jpg",
            thumbnailUrl = "https://example.com/thumb.jpg"
        ),
        InlineQueryResultGif(
            id = "3",
            gifUrl = "https://example.com/animation.gif",
            thumbnailUrl = "https://example.com/thumb.gif"
        )
    )

    answerInlineQuery(
        query,
        results,
        cacheTime = 300,
        isPersonal = false,
        button = InlineQueryResultsButton("Open Bot", StartParameter("param"))
    )
}

onChosenInlineResult { result ->
    val resultId = result.resultId
    val query = result.query
}

FSM (Finite State Machine)

FSM imports — easy 2 mis-import; pin these 4 ktgbotapi 33.1.0:

import dev.inmo.tgbotapi.extensions.behaviour_builder.fsm.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.fsm.strictlyOn
import dev.inmo.micro_utils.fsm.common.State
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitText
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildBehaviourWithFSMAndStartLongPolling

Notes:

  • State lives in microutils.fsm.common (NOT tgbotapi.*) — separate artifact dev.inmo:microutils.fsm.common.
  • strictlyOn + FSM context builders live under extensions.behaviourbuilder.fsm (artifact tgbotapi.behaviourbuilder.fsm).
  • waitText / other wait* expectations live under extensions.behaviour_builder.expectations, NOT under .fsm.
  • buildBehaviourWithFSMAndStartLongPolling sits directly in extensions.behaviour_builder (parent package), not .fsm.
// Define states
sealed interface BotState : State {
    override val context: IdChatIdentifier
    data class WaitingName(override val context: IdChatIdentifier) : BotState
    data class WaitingAge(override val context: IdChatIdentifier, val name: String) : BotState
}

// Bot with FSM
bot.buildBehaviourWithFSMAndStartLongPolling<BotState> {

    onCommand("start") { message ->
        startChain(BotState.WaitingName(message.chat.id))
    }

    strictlyOn<BotState.WaitingName> { state ->
        send(state.context, "Enter your name:")
        val name = waitText { it.chat.id == state.context }.first().content.text
        BotState.WaitingAge(state.context, name)  // transition
    }

    strictlyOn<BotState.WaitingAge> { state ->
        send(state.context, "Enter your age:")
        val age = waitText { it.chat.id == state.context }.first().content.text
        send(state.context, "Name: ${state.name}, Age: $age")
        null  // end chain
    }
}.second.join()

Chat Management

// Get chat info
val chat = getChat(chatId)

// Get chat member
val member = getChatMember(chatId, userId)

// Kick/ban
banChatMember(chatId, userId)
banChatMember(chatId, userId, untilDate = Instant.now() + 1.hours)

// Unban
unbanChatMember(chatId, userId)

// Restrict
restrictChatMember(chatId, userId, ChatPermissions(
    canSendMessages = false,
    canSendMediaMessages = false
))

// Promote
promoteChatMember(chatId, userId,
    canDeleteMessages = true,
    canPinMessages = true
)

// Set title
setChatAdministratorCustomTitle(chatId, userId, "Custom Title")

File Operations

// Download file
val file = bot.downloadFile(fileId)
val bytes = bot.downloadFileToByteArray(fileId)

// Get file info
val fileInfo = getFile(fileId)
val filePath = fileInfo.filePath

Bot Settings

// Get bot info
val me = getMe()

// Set commands
setMyCommands(listOf(
    BotCommand("start", "Start the bot"),
    BotCommand("help", "Show help")
))

// Set commands for specific scope
setMyCommands(
    commands = listOf(BotCommand("admin", "Admin panel")),
    scope = BotCommandScopeChat(adminChatId)
)

// Delete commands
deleteMyCommands()

// Set description
setMyDescription("Bot description")
setMyShortDescription("Short description")

Error Handling

bot.buildBehaviourWithLongPolling(
    defaultExceptionsHandler = { throwable ->
        when (throwable) {
            is CommonBotException -> println("API error: ${throwable.message}")
            is CancellationException -> { /* ignore */ }
            else -> throwable.printStackTrace()
        }
    }
) {
    // handlers
}

Types Quick Reference

Type Description
ChatId Chat identifier (Long wrapper)
UserId User identifier
MessageId Message identifier
FileId File identifier
IdChatIdentifier Union of ChatId/UserId
CommonMessage<T> Message with content type T
TextContent Text message content
PhotoContent Photo message content
DocumentContent Document message content
PrivateChat Private chat type
GroupChat Group chat type
SupergroupChat Supergroup chat type
ChannelChat Channel chat type