### User Registration Wizard Example Source: https://github.com/vendelieu/telegram-bot/wiki/FSM-and-Conversation-handling A complete example of a user registration wizard using a Finite State Machine (FSM) approach. It handles multiple steps including name input, age validation, underage rejection, confirmation, and final registration. ```kotlin @WizardHandler( trigger = ["/register"], stateManagers = [StringStateManager::class, IntStateManager::class] ) object RegistrationWizard { object NameStep : WizardStep(isInitial = true) { override suspend fun onEntry(ctx: WizardContext) { message { "What is your name?" }.send(ctx.user, ctx.bot) } override suspend fun onRetry(ctx: WizardContext) { message { "Please enter a valid name." }.send(ctx.user, ctx.bot) } override suspend fun validate(ctx: WizardContext): Transition { val name = ctx.update.text?.trim() return if (name.isNullOrBlank() || name.length < 2) { Transition.Retry } else { Transition.Next } } override suspend fun store(ctx: WizardContext): String { return ctx.update.text!!.trim() } } object AgeStep : WizardStep { override suspend fun onEntry(ctx: WizardContext) { message { "How old are you?" }.send(ctx.user, ctx.bot) } override suspend fun onRetry(ctx: WizardContext) { message { "Please enter a valid age (must be a number)." }.send(ctx.user, ctx.bot) } override suspend fun validate(ctx: WizardContext): Transition { val age = ctx.update.text?.toIntOrNull() return when { age == null -> Transition.Retry age < 0 || age > 150 -> Transition.Retry age < 18 -> Transition.JumpTo(UnderageStep::class) else -> Transition.Next } } override suspend fun store(ctx: WizardContext): Int { return ctx.update.text!!.toInt() } } object UnderageStep : WizardStep { override suspend fun onEntry(ctx: WizardContext) { message { "Sorry, you must be 18 or older to register." }.send(ctx.user, ctx.bot) } override suspend fun onRetry(ctx: WizardContext) = Unit override suspend fun validate(ctx: WizardContext): Transition { return Transition.Finish } } object ConfirmationStep : WizardStep { override suspend fun onEntry(ctx: WizardContext) { // Type-safe state access val name: String? = ctx.getState() val age: Int? = ctx.getState() val confirmation = buildString { appendLine("Please confirm your information:") appendLine("Name: $name") appendLine("Age: $age") appendLine() appendLine("Reply 'yes' to confirm or 'no' to start over.") } message { confirmation }.send(ctx.user, ctx.bot) } override suspend fun onRetry(ctx: WizardContext) { message { "Please reply 'yes' or 'no'." }.send(ctx.user, ctx.bot) } override suspend fun validate(ctx: WizardContext): Transition { val response = ctx.update.text?.lowercase()?.trim() return when (response) { "yes" -> Transition.Finish "no" -> Transition.JumpTo(NameStep::class) // Start over else -> Transition.Retry } } } object FinishStep : WizardStep { override suspend fun onEntry(ctx: WizardContext) { val name: String? = ctx.getState() val age: Int? = ctx.getState() // Save to database, send confirmation, etc. message { "Registration complete! Welcome, $name (age $age)." }.send(ctx.user, ctx.bot) } override suspend fun onRetry(ctx: WizardContext) = Unit override suspend fun validate(ctx: WizardContext): Transition { return Transition.Finish } } } ``` -------------------------------- ### Manual Gradle Setup with KSP Source: https://github.com/vendelieu/telegram-bot/blob/master/README.md Configure your build.gradle.kts manually by adding dependencies and the KSP processor if not using the plugin. ```gradle plugins { // ... id("com.google.devtools.ksp") version "2.3.8" } dependencies { // ... implementation("eu.vendeli:telegram-bot:9.5.0") ksp("eu.vendeli:ktnip:9.5.0") } ``` -------------------------------- ### Custom UserData Implementation Example Source: https://github.com/vendelieu/telegram-bot/wiki/Bot-Context Example of providing a custom implementation for UserData. Ensure you run the ksp task after defining your custom class. ```kotlin import eu.vendeli.tgbot.annotations.CtxProvider import eu.vendeli.tgbot.interfaces.ctx.UserData @CtxProvider class MyRedis : UserData { // ... implementation details ... } ``` -------------------------------- ### Install Pre-push Hook Source: https://github.com/vendelieu/telegram-bot/blob/master/CONTRIBUTING.md Installs a pre-push hook to automatically run code formatting and validation before each push. This helps maintain code quality. ```bash ./scripts/install-hooks.sh ``` -------------------------------- ### Configure Bot Sessions Source: https://github.com/vendelieu/telegram-bot/wiki/Bot-configuration Example of configuring session management for the bot, specifying the key strategy and storage mechanism. ```kotlin val bot = TelegramBot("BOT_TOKEN") { sessions { keyStrategy = SessionKeyStrategy.Auto storage = InMemorySessionStorage() } } ``` -------------------------------- ### Minimal Functional DSL Example Source: https://github.com/vendelieu/telegram-bot/wiki/Handlers Demonstrates the basic structure of setting up a bot functionality using `bot.setFunctionality` and handling chosen inline results. ```kotlin suspend fun main() { val bot = TelegramBot("BOT_TOKEN") bot.setFunctionality { onChosenInlineResult { println("got a result ${update.chosenInlineResult.resultId} from ${update.user}") } } } ``` -------------------------------- ### Real-World Example: Authentication, Metrics, and Logging Source: https://github.com/vendelieu/telegram-bot/wiki/Interceptors-(middleware) Demonstrates setting up interceptors for authentication, permission checks, measuring handler execution time, and logging bot activities. ```kotlin suspend fun main() { val bot = TelegramBot("BOT_TOKEN") // Setup phase: Check if user is authenticated bot.update.pipeline.intercept(ProcessingPipePhase.Setup) { context -> val user = context.update.userOrNull ?: return@intercept if (!isAuthenticated(user.id)) { message { "Please authenticate first using /login" } .send(user, context.bot) context.finish() } } // PreInvoke phase: Start timer and check permissions bot.update.pipeline.intercept(ProcessingPipePhase.PreInvoke) { context -> val activity = context.activity ?: return@intercept val user = context.update.userOrNull ?: return@intercept // Check if user has permission for this specific handler if (!hasPermission(user.id, activity)) { message { "You don't have permission to use this command." } .send(user, context.bot) context.finish() return@intercept } // Start timer // store start time } // PostInvoke phase: Log and cleanup bot.update.pipeline.intercept(ProcessingPipePhase.PostInvoke) { context -> val activity = context.activity ?: return@intercept val startTime = // get start time if (startTime != null) { val duration = System.currentTimeMillis() - startTime val logger = context.bot.config.loggerFactory.get("Metrics") logger.info( "Handler ${activity::class.simpleName} took ${duration}ms " + "for user ${context.update.userOrNull?.id}" ) } } bot.handleUpdates() } ``` -------------------------------- ### Focused Wizard Step Example Source: https://github.com/vendelieu/telegram-bot/wiki/FSM-and-Conversation-handling Illustrates the best practice of keeping wizard steps focused on a single responsibility. This improves modularity and maintainability. ```kotlin // ✅ Good - focused step object EmailStep : WizardStep { // Only handles email collection } // ❌ Avoid - too much logic object PersonalInfoStep : WizardStep { // Handles name, email, phone, address... } ``` -------------------------------- ### Meaningful Wizard Step Naming Example Source: https://github.com/vendelieu/telegram-bot/wiki/FSM-and-Conversation-handling Shows the importance of using descriptive names for wizard steps to enhance code readability and understanding. ```kotlin // ✅ Good object EmailVerificationStep : WizardStep // ❌ Avoid object Step2 : WizardStep ``` -------------------------------- ### Interceptor Order Example Source: https://github.com/vendelieu/telegram-bot/wiki/Interceptors-(middleware) Demonstrates the importance of ordering interceptors. More general checks should be registered before more specific ones. ```kotlin // More general checks first bot.update.pipeline.intercept(ProcessingPipePhase.Setup) { // Global ban check } // More specific checks later bot.update.pipeline.intercept(ProcessingPipePhase.Validation) { // Handler-specific permission check } ``` -------------------------------- ### Catch deeplink in start command Source: https://github.com/vendelieu/telegram-bot/wiki/Update-parsing Use @ParamMapping("param_1") to capture deeplink information when a user starts a command, such as the /start command. ```kotlin @CommandHandler(["/start"]) suspend fun start(@ParamMapping("param_1") deeplink: String?, user: User, bot: TelegramBot) { message { "deeplink is $deeplink" }.send(to = user, via = bot) } ``` -------------------------------- ### Gradle Build Script with KSP Plugin Source: https://github.com/vendelieu/telegram-bot/blob/master/README.md Add the KSP plugin and the library plugin to your build.gradle.kts file for project setup. ```gradle plugins { // ... id("com.google.devtools.ksp") version "2.3.8" id("eu.vendeli.telegram-bot") version "9.5.0" } ``` -------------------------------- ### Focused Interceptor Example Source: https://github.com/vendelieu/telegram-bot/wiki/Interceptors-(middleware) Demonstrates a good practice of keeping interceptors focused on a single task. This example checks if a user is banned before proceeding. ```kotlin bot.update.pipeline.intercept(ProcessingPipePhase.Setup) { context -> if (isBanned(context.update.userOrNull?.id)) { context.finish() } } ``` -------------------------------- ### Ephemeral Order Flow - Start Source: https://github.com/vendelieu/telegram-bot/wiki/Sessions Initiates an order session, prompting the user for their order. Messages sent within the `with(order)` block are auto-tracked. ```kotlin @CommandHandler(["/order"]) suspend fun startOrder( @SessionQualifier("order") order: Session, user: User, bot: TelegramBot, ) { with(order) { message { "What would you like to order?" }.send(user, bot) // auto-tracked } } ``` -------------------------------- ### Register Interceptors in Telegram Bot Pipeline Source: https://github.com/vendelieu/telegram-bot/wiki/Interceptors-(middleware) Register interceptors at specific phases of the bot's update processing pipeline (Setup, PreInvoke, PostInvoke). This example demonstrates checking for banned users, measuring handler duration, and logging. ```kotlin suspend fun main() { val bot = TelegramBot("BOT_TOKEN") // Register an interceptor for the Setup phase bot.update.pipeline.intercept(ProcessingPipePhase.Setup) { context -> // Check if user is banned val user = context.update.userOrNull if (user != null && isBanned(user.id)) { context.finish() // Stop processing return@intercept } } // Register an interceptor for the PreInvoke phase bot.update.pipeline.intercept(ProcessingPipePhase.PreInvoke) { context -> val startTime = System.currentTimeMillis() // store start time } // Register an interceptor for the PostInvoke phase bot.update.pipeline.intercept(ProcessingPipePhase.PostInvoke) { context -> val startTime = // get start time if (startTime != null) { val duration = System.currentTimeMillis() - startTime println("Handler took ${duration}ms") } } bot.handleUpdates() } ``` -------------------------------- ### Custom Database State Manager Source: https://github.com/vendelieu/telegram-bot/wiki/FSM-and-Conversation-handling Implement `WizardStateManager` to manage step state using a custom storage solution like a database. This example shows loading, saving, and deleting state from a database. ```kotlin class DatabaseStateManager : WizardStateManager { override suspend fun get( key: KClass, reference: UserChatReference ): String? { // Load from database return database.getWizardState(reference.userId, key.qualifiedName) } override suspend fun set( key: KClass, reference: UserChatReference, value: String ) { // Save to database database.saveWizardState(reference.userId, key.qualifiedName, value) } override suspend fun del( key: KClass, reference: UserChatReference ) { // Delete from database database.deleteWizardState(reference.userId, key.qualifiedName) } } ``` -------------------------------- ### Type-Safe State Access Example Source: https://github.com/vendelieu/telegram-bot/wiki/FSM-and-Conversation-handling Demonstrates the preferred method for accessing wizard state using generated type-safe extension functions. Avoids manual casting and improves code safety. ```kotlin // ✅ Good - type-safe val name: String? = ctx.getState() // ❌ Avoid - loses type safety val name = ctx.getState(NameStep::class) as? String ``` -------------------------------- ### Send Email Prompt Source: https://github.com/vendelieu/telegram-bot/wiki/FSM-and-Conversation-handling Use this snippet to prompt the user for their email address, specifying the expected format. Ensure the message is clear and includes an example. ```kotlin override suspend fun onEntry(ctx: WizardContext) { message { "Please enter your email address:\n" + "(Format: user@example.com)" }.send(ctx.user, ctx.bot) } ``` -------------------------------- ### Get and Use a Session Source: https://github.com/vendelieu/telegram-bot/wiki/Sessions Obtain a session for a specific chat and user, then send messages through it. Messages sent within the `with(session)` block are automatically tracked. ```kotlin val session = bot.sessions.get(chatId = chat.id, userId = user.id) // Both incoming and outgoing are tracked automatically — just send through the session. with(session) { message { "Hi, what's up?" }.send(bot) // auto-tracked as Outgoing } // later — wipe everything we sent and received in this slice session.clear() ``` -------------------------------- ### Configure Telegram Bot with Kotlin Source: https://github.com/vendelieu/telegram-bot/wiki/Bot-configuration This snippet shows a comprehensive configuration for a Telegram bot using the TelegramBot class. It includes settings for the bot's identifier, API host, test environment, input listeners, class management, HTTP client timeouts, logging levels, update listener dispatchers, and command parsing delimiters. Use this for detailed bot setup. ```kotlin val bot = TelegramBot("TOKEN") { identifier = "MyBot", apiHost = "https://api.telegram.org", isTestEnv = true, inputListener = InputListenerMapImpl(), classManager = ClassManagerImpl(), httpClient { requestTimeoutMillis = 5000L connectTimeoutMillis = 3000L socketTimeoutMillis = 2000L } logging { botLogLevel = LogLvl.DEBUG httpLogLevel = HttpLogLevel.BODY } updatesListener { dispatcher = Dispatchers.IO processingDispatcher = Dispatchers.Unconfined pullingDelay = 1000L } commandParsing { commandDelimiter = '*' parametersDelimiter = '&' restrictSpacesInCommands = true } } ``` -------------------------------- ### Building Reply Keyboard Markup Source: https://github.com/vendelieu/telegram-bot/wiki/Actions Create menu buttons for reply keyboards using the `replyKeyboardMarkup` builder. Buttons can be added with the unary plus operator, and new rows can be started with `br()`. ```kotlin message{ "Test" }.replyKeyboardMarkup { + "Menu button" // you can add buttons by using unary plus operator + "Menu button 2" br() // go to second row "Send polls 👀" requestPoll true // button with parameter options { resizeKeyboard = true } }.send(user, bot) ``` -------------------------------- ### Get Independent Sessions with Qualifiers Source: https://github.com/vendelieu/telegram-bot/wiki/Sessions Demonstrates how to obtain independent sessions for the same chat and user by providing a unique qualifier string. This allows for isolating different conversational states or functionalities. ```kotlin val wizard = bot.sessions.get(chat.id, user.id, qualifier = "wizard") val support = bot.sessions.get(chat.id, user.id, qualifier = "support") ``` -------------------------------- ### Custom User Resolver with Autowiring Source: https://github.com/vendelieu/telegram-bot/wiki/Activity-invocation Implement a custom type for passing by implementing the Autowiring interface and marking it with the @Injectable annotation. This example shows how to resolve a UserRecord. ```kotlin @Injectable object UserResolver : Autowiring { override suspend fun get(update: ProcessedUpdate, bot: TelegramBot): UserRecord? { return userRepository.getUserByTgId(update.user.id) } } ``` -------------------------------- ### Clean Up Wizard State Source: https://github.com/vendelieu/telegram-bot/wiki/FSM-and-Conversation-handling Provides an example of how to manually clear specific wizard states when needed, such as during a cancellation flow. This ensures that old or irrelevant state data is removed. ```kotlin object CancelStep : WizardStep { override suspend fun onEntry(ctx: WizardContext) { // Clear all wizard state ctx.delState() ctx.delState() message { "Registration cancelled." }.send(ctx.user, ctx.bot) } } ``` -------------------------------- ### Basic Command and Input Handling Source: https://github.com/vendelieu/telegram-bot/blob/master/README.md Demonstrates setting up command handlers for specific commands like /start and input handlers for conversational flows. It also shows how to set up a regex-based handler for matching patterns. ```kotlin suspend fun main() { val bot = TelegramBot("BOT_TOKEN") bot.handleUpdates() // start long-polling listener } @CommandHandler(["/start"]) suspend fun start(user: User, bot: TelegramBot) { sendMessage { "Hello, what's your name?" }.send(user, bot) bot.inputListener[user] = "conversation" } @InputHandler(["conversation"]) @Guard(UserPresentGuard::class) suspend fun startConversation(update: ProcessedUpdate, user: User, bot: TelegramBot) { sendMessage { "Nice to meet you, ${update.text}" }.send(user, bot) sendMessage { "What is your favorite food?" }.send(user, bot) bot.inputListener.set(user) { "conversation-2step" } // another way to set input } @CommonHandler.Regex("blue colo?r") suspend fun color(user: User, bot: TelegramBot) { message { "Oh you also like blue color?" }.send(user, bot) } //.. ``` -------------------------------- ### Input Handling with Functional DSL Source: https://github.com/vendelieu/telegram-bot/wiki/Handlers Illustrates setting up a command that prompts for user input and then handling that specific input. ```kotlin bot.setFunctionality { onCommand("/start") { message { "Hello, what's your name?" }.send(user, bot) bot.inputListener[user] = "testInput" } onInput("testInput") { message { "Hey, nice to meet you, ${update.text}" }.send(user, bot) } } ``` -------------------------------- ### Start Long Polling Listener Source: https://github.com/vendelieu/telegram-bot/wiki/Activites-and-Processors Initiate the long polling mechanism to continuously receive updates from Telegram by calling `bot.handleUpdates()`. This method starts listening for messages. ```kotlin bot.handleUpdates() ``` -------------------------------- ### Configure Webhook Server and Bot Source: https://github.com/vendelieu/telegram-bot/wiki/Web-starters-(Spring-and-Ktor) Initializes a webhook server with manual configuration and declares a bot instance. ```kotlin fun main() = runBlocking { serveWebhook { server { HOST = "0.0.0.0" PORT = 8080 SSL_PORT = 8443 PEM_PRIVATE_KEY_PATH = "/etc/letsencrypt/live/example.com/privkey.pem" PEM_CHAIN_PATH = "/etc/letsencrypt/live/example.com/fullchain.pem" PEM_PRIVATE_KEY = "pem_changeit".toCharArray() KEYSTORE_PATH = "/etc/ssl/certs/java/cacerts/bot_keystore.jks" KEYSTORE_PASSWORD = "changeit".toCharArray() // Set other configuration parameters as needed } declareBot { token = "YOUR_BOT_TOKEN" // Configure other bot settings } // Add more bots or set other parameters if needed } } ``` -------------------------------- ### Initialize Bot with Specific Package Source: https://github.com/vendelieu/telegram-bot/wiki/Activites-and-Processors When using KSP arguments to specify packages, you must also specify the package in the bot instance itself for correct processing. ```kotlin fun main() = runBlocking { val bot = TelegramBot("BOT_TOKEN", "com.example.mybot") bot.handleUpdates() // start long-polling listener } ``` -------------------------------- ### Add Ktor Starter Dependency Source: https://github.com/vendelieu/telegram-bot/wiki/Web-starters-(Spring-and-Ktor) Add this dependency to your main build.gradle file to use the Ktor starter for creating webhook servers. ```gradle dependencies { implementation("eu.vendeli:ktor-starter:x.y.z") // there // change x.y.z to current library version } ``` -------------------------------- ### WizardStateManager Interface Source: https://github.com/vendelieu/telegram-bot/wiki/FSM-and-Conversation-handling The WizardStateManager interface defines methods for getting, setting, and deleting state for a given step and user chat reference. ```kotlin import eu.vendeli.tgbot.core.WizardStep import eu.vendeli.tgbot.core.UserChatReference import kotlin.reflect.KClass interface WizardStateManager { suspend fun get(key: KClass, reference: UserChatReference): T? suspend fun set(key: KClass, reference: UserChatReference, value: T) suspend fun del(key: KClass, reference: UserChatReference) } ``` -------------------------------- ### Companion Options for Command Handlers Source: https://github.com/vendelieu/telegram-bot/wiki/Handlers Shows how to apply rate limits, guards, and custom argument parsers directly within `onCommand` definitions. ```kotlin bot.setFunctionality { onCommand("/expensive", rateLimits = RateLimits(rate = 5, period = 60_000)) { message { "Processing..." }.send(user, bot) } onCommand("/admin", guard = AdminGuard::class) { message { "Admin command executed" }.send(user, bot) } onCommand("/custom", argParser = CustomArgParser::class) { message { "Parameters: $parameters" }.send(user, bot) } } ``` -------------------------------- ### Input Chain for Multi-Step Conversations Source: https://github.com/vendelieu/telegram-bot/wiki/Handlers Demonstrates creating a multi-step input flow using `inputChain`, including break conditions and subsequent actions. ```kotlin bot.setFunctionality { inputChain("conversation") { message { "Nice to meet you, ${update.text}" }.send(user, bot) message { "What is your favorite food?" }.send(user, bot) }.breakIf({ update.text == "peanut butter" }) { // chain break condition message { "Oh, too bad, I'm allergic to it." }.send(user, bot) }.andThen { // next step when the break condition didn't match message { "Great choice!" }.send(user, bot) } } ``` -------------------------------- ### Run Gradle Prepare Release Source: https://github.com/vendelieu/telegram-bot/blob/master/CONTRIBUTING.md Formats code, validates the API, updates KDocs, and runs API violations checks. This is a comprehensive task to ensure code quality before release. ```bash ./gradlew prepareRelease ``` -------------------------------- ### Conditional Interceptor Logic Source: https://github.com/vendelieu/telegram-bot/wiki/Interceptors-(middleware) Register a custom interceptor that conditionally applies logic based on the handler's class name. This example runs before the invoke phase. ```kotlin bot.update.pipeline.intercept(ProcessingPipePhase.PreInvoke) { context -> val activity = context.activity ?: return@intercept // Only apply to specific handlers if (activity::class.simpleName?.contains("Admin") == true) { // Admin-specific logic checkAdminPermissions(context) } } ``` -------------------------------- ### Server Configuration Source: https://github.com/vendelieu/telegram-bot/wiki/Web-starters-(Spring-and-Ktor) Configures the server settings including host, port, and SSL parameters using either EnvConfiguration or ManualConfiguration. ```APIDOC ## Server Configuration ### Description Configures the server environment for the Telegram bot. Supports manual configuration via the `server {}` block or environment variables prefixed with `KTGRAM_`. ### Parameters #### Request Body - **HOST** (string) - Optional - The hostname or IP address of the server. - **PORT** (int) - Optional - The port number for the server. - **SSL_PORT** (int) - Optional - The port number for SSL/TLS connections. - **PEM_PRIVATE_KEY_PATH** (string) - Optional - Path to the PEM private key file. - **PEM_CHAIN_PATH** (string) - Optional - Path to the PEM certificate chain file. - **PEM_PRIVATE_KEY** (char[]) - Optional - The PEM private key password. - **KEYSTORE_PATH** (string) - Optional - Path to the Java KeyStore file. - **KEYSTORE_PASSWORD** (char[]) - Optional - Password for the KeyStore. - **KEY_ALIAS** (string) - Optional - Alias for the key in the KeyStore. - **SSL_ON** (boolean) - Optional - Whether SSL/TLS is enabled. Defaults to true. ``` -------------------------------- ### Create a Pipeline Interceptor Lambda Source: https://github.com/vendelieu/telegram-bot/wiki/Interceptors-(middleware) Define an interceptor using a lambda expression for concise inline definitions. This example shows logging the processed update ID. ```kotlin val loggingInterceptor = PipelineInterceptor { context -> val logger = context.bot.config.loggerFactory.get("MyInterceptor") logger.info("Processing update #${context.update.updateId}") } ``` -------------------------------- ### Command Handlers with Functional DSL Source: https://github.com/vendelieu/telegram-bot/wiki/Handlers Shows how to define handlers for specific commands, including exact string matching and regex-based matching. ```kotlin bot.setFunctionality { // Regular command onCommand("/start") { message { "Hello" }.send(user, bot) } // Regex-based command matching onCommand("""(red|green|blue)""".toRegex()) { message { "you typed ${update.text} color" }.send(user, bot) } } ``` -------------------------------- ### Configure Multiple Packages with KSP Arguments Source: https://github.com/vendelieu/telegram-bot/wiki/Activites-and-Processors Specify multiple search packages using a semicolon-separated string for KSP arguments when not using the Gradle plugin. ```gradle ksp { arg("package", "com.example.mybot;com.example.mybot2") } ``` -------------------------------- ### Handle Email Validation Error Source: https://github.com/vendelieu/telegram-bot/wiki/FSM-and-Conversation-handling Implement this to gracefully handle and inform the user about invalid email formats. Provide a clear error message and an example of the correct format. ```kotlin override suspend fun onRetry(ctx: WizardContext) { message { "Invalid email format. Please try again.\n" + "Example: user@example.com" }.send(ctx.user, ctx.bot) } ``` -------------------------------- ### Generated Type-Safe State Functions Source: https://github.com/vendelieu/telegram-bot/wiki/FSM-and-Conversation-handling KSP generates these inline functions for type-safe access to state within a WizardContext. Use them to get, set, or delete state for a specific WizardStep. ```kotlin suspend inline fun WizardContext.getState(): String? suspend inline fun WizardContext.setState(value: String) suspend inline fun WizardContext.delState() ``` -------------------------------- ### Configure Packages with KSP Arguments Source: https://github.com/vendelieu/telegram-bot/wiki/Activites-and-Processors Alternatively, configure the search packages using KSP arguments if not using the Gradle plugin. This is useful for direct KSP integration. ```gradle ksp { arg("package", "com.example.mybot") } ``` -------------------------------- ### Input Handling Flowchart Source: https://github.com/vendelieu/telegram-bot/wiki/Home Demonstrates the flow when an update does not match a command, showing how it checks for user-specific input-waiting points before falling back to common or unprocessed handlers. ```mermaid flowchart LR Upd["Update arrives"] --> Cmd{"matches a command?" Cmd -- no --> InpPoint{"user has
an input-waiting point?" InpPoint -- yes --> Hit["Invoke matching @InputHandler
(input is consumed)"] InpPoint -- no --> Fallback["Fall through to Common / Unprocessed"] ``` -------------------------------- ### DSL-based Bot Functionality Configuration Source: https://github.com/vendelieu/telegram-bot/blob/master/README.md Shows how to configure bot functionality using a Domain Specific Language (DSL). This includes defining command handlers, input chains with break conditions, and subsequent actions. ```kotlin fun main() = runBlocking { val bot = TelegramBot("BOT_TOKEN") bot.setFunctionality { onCommand("/start") { message { "Hello, what's your name?" }.send(user, bot) bot.inputListener[user] = "conversation" } inputChain("conversation") { message { "Nice to meet you, ${message.text}" }.send(update.getUser(), bot) message { "What is your favorite food?" }.send(update.getUser(), bot) }.breakIf({ message.text == "peanut butter" }) { // chain break condition message { "Oh, too bad, I'm allergic to it." }.send(update.getUser(), bot) // action that will be applied when match }.andThen { // next input point if break condition doesn't match } } bot.handleUpdates() } ``` -------------------------------- ### Configure Bot with Lambda Source: https://github.com/vendelieu/telegram-bot/wiki/Bot-configuration Use a lambda to configure bot properties like input listeners, class managers, and logging levels. ```kotlin // ... val bot = TelegramBot("BOT_TOKEN") { inputListener = RedisInputListenerImpl() classManager = KoinClassManagerImpl() logging { botLogLevel = LogLvl.DEBUG } } // ... ``` -------------------------------- ### Configure Session Management Source: https://github.com/vendelieu/telegram-bot/wiki/Sessions Sets up session management with auto key strategy and in-memory storage. The `sessions` block is only required when overriding defaults. ```kotlin val bot = TelegramBot("BOT_TOKEN") { sessions { keyStrategy = SessionKeyStrategy.Auto storage = InMemorySessionStorage() // managerFactory = SessionManagerFactory { bot, cfg -> CustomSessionManager(bot, cfg) } } } ``` -------------------------------- ### Configure Bot with ConfigLoader Source: https://github.com/vendelieu/telegram-bot/wiki/Bot-configuration Instantiate TelegramBot with a ConfigLoader implementation to load settings from external sources. ```kotlin val bot = TelegramBot(ConfigLoaderImpl) ``` -------------------------------- ### Configure Multiple Packages with Gradle Plugin Source: https://github.com/vendelieu/telegram-bot/wiki/Activites-and-Processors Configure the ktGram Gradle plugin to search for activities across multiple packages, allowing for modular bot structures. ```gradle ktGram { packages = listOf("com.example.mybot", "com.example.mybot2") } ``` -------------------------------- ### Configure Packages with Gradle Plugin Source: https://github.com/vendelieu/telegram-bot/wiki/Activites-and-Processors Use the ktGram Gradle plugin to specify the packages where activities will be searched. This helps limit the scope of discovery. ```gradle ktGram { packages = listOf("com.example.mybot") } ``` -------------------------------- ### Generalize Method Options Source: https://github.com/vendelieu/telegram-bot/wiki/Useful-utilities-and-tips Create extension functions to apply common optional parameters and reduce boilerplate code. ```kotlin @Suppress("NOTHING_TO_INLINE") inline fun T.markdownMode(crossinline block: O.() -> Unit = {}): T where T : TgAction, T : OptionsFeature, O : Options, O : OptionsParseMode = options { parseMode = ParseMode.Markdown block() } // ... and in your code message { "test" }.markdownMode().send(to, via) ``` -------------------------------- ### Resource Cleanup with Interceptors Source: https://github.com/vendelieu/telegram-bot/wiki/Interceptors-(middleware) Illustrates how to manage resources opened in `PreInvoke` by cleaning them up in `PostInvoke`. This ensures resources like timers are properly closed. ```kotlin var timer: Timer? = null bot.update.pipeline.intercept(ProcessingPipePhase.PreInvoke) { context -> timer = Timer() context.additionalContext["timer"] = timer } bot.update.pipeline.intercept(ProcessingPipePhase.PostInvoke) { context -> val timer = context.additionalContext["timer"] as? Timer timer?.stop() } ``` -------------------------------- ### Add Spring Starter Dependency Source: https://github.com/vendelieu/telegram-bot/wiki/Web-starters-(Spring-and-Ktor) Include this dependency in your build.gradle or pom.xml to use the Spring Boot starter for Telegram bots. ```gradle dependencies { implementation 'eu.vendeli:spring-starter:' } ``` -------------------------------- ### Define a WizardStep for Name Input Source: https://github.com/vendelieu/telegram-bot/wiki/FSM-and-Conversation-handling Implements a basic wizard step to collect user's name. It prompts the user, validates non-empty input, and stores the name. Use this for simple data collection steps. ```kotlin object NameStep : WizardStep(isInitial = true) { override suspend fun onEntry(ctx: WizardContext) { message { "What is your name?" }.send(ctx.user, ctx.bot) } override suspend fun onRetry(ctx: WizardContext) { message { "Name cannot be empty. Please try again." }.send(ctx.user, ctx.bot) } override suspend fun validate(ctx: WizardContext): Transition { return if (ctx.update.text.isNullOrBlank()) { Transition.Retry } else { Transition.Next } } override suspend fun store(ctx: WizardContext): String { return ctx.update.text!! } } ``` -------------------------------- ### Processing Responses with sendReturning Source: https://github.com/vendelieu/telegram-bot/blob/master/README.md Illustrates how to use the `sendReturning()` method for more control over request flow and response processing. It shows how to handle potential failures by accessing error details. ```kotlin message { "test" }.sendReturning(user, bot).onFailure { println("code: ${it.errorCode} description: ${it.description}") } ``` -------------------------------- ### Stateless Wizard Step Source: https://github.com/vendelieu/telegram-bot/wiki/FSM-and-Conversation-handling Implement a stateless step by returning `null` from the `store()` method. This is suitable for steps that do not need to persist any data. ```kotlin object ConfirmationStep : WizardStep { override suspend fun store(ctx: WizardContext): Any? = null // ... rest of implementation } ``` -------------------------------- ### Combining DSL and Annotation Handlers Source: https://github.com/vendelieu/telegram-bot/wiki/Handlers Illustrates that both annotation-based and Functional DSL handlers can coexist and be registered and dispatched in the same bot. ```kotlin @CommandHandler(["/register"]) suspend fun register(user: User, bot: TelegramBot) { message { "Registration started" }.send(user, bot) } bot.setFunctionality { onCommand("/help") { message { "Available commands: /register, /help" }.send(user, bot) } } ``` -------------------------------- ### Run Gradle API Dump Source: https://github.com/vendelieu/telegram-bot/blob/master/CONTRIBUTING.md Generates an API dump for the project. This task is useful for understanding the current API structure. ```bash ./gradlew apiDump ``` -------------------------------- ### Using Contextual Entities for Formatting Source: https://github.com/vendelieu/telegram-bot/wiki/Actions Format text within messages using contextual builders like `bold`. This allows for inline formatting without manually specifying entity types and ranges. ```kotlin message { "usual text " - bold { "this is bold text" } - " continue usual" }.send(user, bot) ``` -------------------------------- ### Building Inline Keyboard Markup Source: https://github.com/vendelieu/telegram-bot/wiki/Actions Construct inline buttons with callbacks or URLs using the `inlineKeyboardMarkup` builder. Buttons in the same block appear on the same row by default. ```kotlin message{ "Test" }.inlineKeyboardMarkup { "name" callback "callbackData" // "buttonName" url "https://google.com" //--- these two buttons will be in the same row. newLine() // or br() "otherButton" webAppInfo "data" // this will be in other row // you can also use a different style within the builder: callbackData("buttonName") { "callbackData" } }.send(user, bot) ``` -------------------------------- ### Integrate Guard with Command Handler Source: https://github.com/vendelieu/telegram-bot/wiki/Guards Demonstrates defining a Guard class and applying it to a command handler using the @Guard annotation. ```kotlin // define somewhere your guard class that implements Guard interface object YourGuard : Guard { override suspend fun condition(user: User?, update: ProcessedUpdate, bot: TelegramBot): Boolean { // write your condition here } } // ... @CommandHandler(["yourCommand"]) @Guard(YourGuard::class) // InputHandler also is supported fun command(bot: TelegramBot) { // command body } ``` -------------------------------- ### Basic CommandHandler Source: https://github.com/vendelieu/telegram-bot/wiki/Handlers Marks functions that process specific commands with keywords and scopes. ```kotlin import eu.vendeli.tgbot.annotations.CommandHandler @CommandHandler(["text"]) suspend fun test(user: User, bot: TelegramBot) { //... } ``` -------------------------------- ### Bypass Pre-push Hook Source: https://github.com/vendelieu/telegram-bot/blob/master/CONTRIBUTING.md Demonstrates how to bypass the pre-push hook for a single push. Use SKIP_PRE_PUSH=1 or --no-verify. ```bash SKIP_PRE_PUSH=1 ./gradlew build ``` ```bash ./gradlew build --no-verify ``` -------------------------------- ### Sending a Message with Options Source: https://github.com/vendelieu/telegram-bot/wiki/Actions Use the `options` block to set parameters like `parseMode` for messages. This is useful for formatting message content. ```kotlin message{ "*Test*" }.options { parseMode = ParseMode.Markdown }.send(user, bot) ``` -------------------------------- ### Gradle Configuration for Snapshot Repository Source: https://github.com/vendelieu/telegram-bot/blob/master/README.md Configure your Gradle build to use the snapshot repository for development versions. This can be done via the ktGram plugin or manually. ```gradle ktGram { forceVersion = "branch-xxxxxx~xxxxxx" addSnapshotRepo = true } ``` ```gradle repositories { mavenCentral() // ... maven("https://mvn.vendeli.eu/telegram-bot") // this } ``` -------------------------------- ### Implement Custom Logic Guard Source: https://github.com/vendelieu/telegram-bot/wiki/Guards A boilerplate for defining custom validation logic for command execution. ```kotlin override suspend fun condition(user: User?, update: ProcessedUpdate, bot: TelegramBot): Boolean { // Custom logic to determine if the command should be executed } ``` -------------------------------- ### Common Matchers with Functional DSL Source: https://github.com/vendelieu/telegram-bot/wiki/Handlers Demonstrates using `common` to create handlers that match arbitrary strings or regex patterns. ```kotlin bot.setFunctionality { common("hello") { message { "Hi there!" }.send(user, bot) } common("""\d+""" .toRegex()) { message { "You sent a number!" }.send(user, bot) } } ``` -------------------------------- ### Manually Adding ReplyKeyboardMarkup Source: https://github.com/vendelieu/telegram-bot/wiki/Actions Construct and send messages with a `ReplyKeyboardMarkup` manually. This allows for programmatic creation of reply keyboard buttons. ```kotlin message{ "*Test*" }.markup { ReplyKeyboardMarkup( KeyboardButton("Test menu button") ) }.send(user, bot) ``` -------------------------------- ### Conditional Transitions with JumpTo Source: https://github.com/vendelieu/telegram-bot/wiki/FSM-and-Conversation-handling Use `Transition.JumpTo` to navigate to a specific step based on user input. This is useful for creating branching conversation flows. ```kotlin override suspend fun validate(ctx: WizardContext): Transition { val choice = ctx.update.text?.lowercase() return when (choice) { "premium" -> Transition.JumpTo(PremiumStep::class) "basic" -> Transition.JumpTo(BasicStep::class) else -> Transition.Retry } } ``` -------------------------------- ### Define a Basic Wizard Source: https://github.com/vendelieu/telegram-bot/wiki/FSM-and-Conversation-handling Define a wizard using the @WizardHandler annotation with a trigger command. Each step is a nested object inheriting from WizardStep. ```kotlin import eu.vendeli.tgbot.annotations.WizardHandler import eu.vendeli.tgbot.core.WizardStep import eu.vendeli.tgbot.types.UpdateType @WizardHandler(trigger = ["/survey"]) object SurveyWizard { object NameStep : WizardStep(isInitial = true) { // ... step implementation } object AgeStep : WizardStep { // ... step implementation } object FinishStep : WizardStep { // ... step implementation } } ``` -------------------------------- ### Session Retrieval with Qualifier Source: https://github.com/vendelieu/telegram-bot/wiki/Sessions Demonstrates how to retrieve independent sessions for the same chat/user by using a qualifier. ```APIDOC ## Get Session with Qualifier ### Description Retrieves a specific session instance for a given chat and user. A `qualifier` can be provided to manage multiple independent sessions for the same chat/user pair. ### Method `bot.sessions.get(chatId: Long, userId: Long?, qualifier: String?)` ### Parameters - **chatId** (Long) - Required - The ID of the chat. - **userId** (Long?) - Required - The ID of the user. - **qualifier** (String?) - Optional - A string to identify an independent session. If omitted, the default session is retrieved. ### Examples ```kotlin // Get a default session val defaultSession = bot.sessions.get(chat.id, user.id) // Get an independent session qualified as "wizard" val wizardSession = bot.sessions.get(chat.id, user.id, qualifier = "wizard") // Get an independent session qualified as "support" val supportSession = bot.sessions.get(chat.id, user.id, qualifier = "support") ``` ``` -------------------------------- ### Adding Captions to Media Files Source: https://github.com/vendelieu/telegram-bot/wiki/Actions Use the `caption` method to attach a text caption to media files like photos. This is useful for providing context or descriptions for sent media. ```kotlin photo { "FILE_ID" }.caption { "Test caption" }.send(user, bot) ``` -------------------------------- ### Usage of Type-Safe State Access Source: https://github.com/vendelieu/telegram-bot/wiki/FSM-and-Conversation-handling Demonstrates how to use the generated type-safe functions within a WizardStep to access and display stored state. Ensure the steps are correctly defined and their states are accessible. ```kotlin object FinishStep : WizardStep { override suspend fun onEntry(ctx: WizardContext) { // Type-safe access - returns String? (nullable) val name: String? = ctx.getState() // Type-safe access - returns Int? (nullable) val age: Int? = ctx.getState() val summary = buildString { appendLine("Name: $name") appendLine("Age: $age") } message { summary }.send(ctx.user, ctx.bot) } override suspend fun onRetry(ctx: WizardContext) = Unit override suspend fun validate(ctx: WizardContext): Transition { return Transition.Finish } } ``` -------------------------------- ### Command Handling Scopes Flowchart Source: https://github.com/vendelieu/telegram-bot/wiki/Home Explains how commands are matched based on both text payload and the type of update received, leading to the best-matching @CommandHandler function. ```mermaid flowchart LR Text["text payload
(e.g. /start)"] --> Match["CommandHandler lookup"] UpdType["update type
(MESSAGE, CALLBACK_QUERY, ...)"] --> Match Match --> Handler["Best-matching
@CommandHandler function"] ``` -------------------------------- ### Configure Multiple Bots in Spring Boot Source: https://github.com/vendelieu/telegram-bot/wiki/Web-starters-(Spring-and-Ktor) Add multiple bot entries under the 'bot' section in application.properties or application.yml to initialize several Telegram bot instances. ```yaml ktgram: bot: - token: YOUR_BOT_TOKEN - token: SECOND_BOT_TOKEN ``` -------------------------------- ### Configure ClassManager Source: https://github.com/vendelieu/telegram-bot/wiki/Useful-utilities-and-tips Redefine the ClassManager interface to integrate custom dependency injection mechanisms during bot initialization. ```kotlin fun main() = runBlocking { val bot = TelegramBot("BOT_TOKEN", "com.example.controllers") { classManager = ClassManagerImpl() } bot.handleUpdates() } ``` -------------------------------- ### Manually Adding InlineKeyboardMarkup Source: https://github.com/vendelieu/telegram-bot/wiki/Actions Construct and send messages with an `InlineKeyboardMarkup` manually. This provides direct control over the keyboard structure. ```kotlin message{ "*Test*" }.markup { InlineKeyboardMarkup( InlineKeyboardButton("test", callbackData = "testCallback") ) }.send(user, bot) ``` -------------------------------- ### Update Processing Priority Flowchart Source: https://github.com/vendelieu/telegram-bot/wiki/Home Illustrates the order of operations for handling an incoming update, prioritizing commands, then user inputs, common handlers, and finally unprocessed updates. UpdateHandlers run in parallel. ```mermaid flowchart TD Start(["Parsed update"]) --> Cmd{"matches a @CommandHandler?" Cmd -- yes --> CmdDone["Invoke command"] Cmd -- no --> Inp{"user has pending input?" Inp -- yes --> InpDone["Invoke @InputHandler"] Inp -- no --> Com{"matches any @CommonHandler?" Com -- yes --> ComDone["Invoke common handler"] Com -- no --> Unp["Invoke @UnprocessedHandler"] Start -. parallel .-> UH["@UpdateHandler — always runs"] ``` -------------------------------- ### Implement User Role Check Guard Source: https://github.com/vendelieu/telegram-bot/wiki/Guards A template for verifying user administrative status within a chat context. ```kotlin override suspend fun condition(user: User?, update: ProcessedUpdate, bot: TelegramBot): Boolean { // Check if the user is an admin in the given chat } ``` -------------------------------- ### Update Type Handlers with Functional DSL Source: https://github.com/vendelieu/telegram-bot/wiki/Handlers Shows how to register a handler that responds to specific types of updates, such as messages and callback queries. ```kotlin bot.setFunctionality { onUpdate(UpdateType.MESSAGE, UpdateType.CALLBACK_QUERY) { println("Received update: ${update.type}") } } ```