Telegram Bot on Kotlin: Introduction

It’s been a while since I posted my first tutorial on tgbotapi and it’s time to start a series of articles already, which should sort it out, how you can break the logic of Telegram bots (and potentially any bots 🙂 ) in general and how to do it in the above-mentioned library in conjunction with the add-on PlaguBot.

What should I start with?

Let’s start with what tools will be used:

All tutorial sources will be available in this repositorythe launch is described in the same place, but in short – just replace the value by key botToken in the repository config and call ./gradlew run --args="config.json". Ideally, you should create a separate config file local.config.json and put the config into it because it won’t end up in the repository, but if you want to use config.json – don’t forget to clean this token if you want to push changes to your fork or repository

About the local.config.json trick

Within the repository .gitignore added two lines:


This means that all files and folders starting with local. will be ignored by the git. So it’s enough to name your configuration local.config.json and it definitely will not get into the repository

My work environment

For actual coding I use Intellij IDEAfrom under Linux (Elementary OS), but practice shows that you can usually work under any other systems (and Linux owners can get by with a text editor and a terminal for building / running)

The code for this tutorial is available in the module Introductionand specifically, we will be most interested in the plugin.

BUT? (Or how it works there)

PlaguBot is designed quite simply: it takes the path to the file, turns it into a config, takes all the plugins from the config, and then loads the bot in two stages:

  • Configuration loading stage – at this stage, you need to declare your objects and other settings in Koin DI

  • The stage of configuring the behavior of the bot – here we are already setting up the reaction to messages from users and so on

Theoretically, you can upload all the settings to the fields of the plugin itself, but…

this doesn’t really fit with the general plugin paradigm. Plugins are inherently factories that should, if necessary, customize the work within several bots

And now the introduction

First (nearly), what we do within the plugin is declare its configuration class:

private class Config(
    val onStartCommandMessage: String

Here we have a private class that will not be available in other classes except for black magic reflections. Actually, in this class we have only one field onStartCommandMessage which we will use later to respond to users.


In fact, the first thing we do is define a logger for the plugin

private val log = logger

logger will create a KSLog object for us for automatic logging with the tag of our plugin. You can see more here.


It’s more convenient to create a separate logger this way, since logger is an extension method to any object, and since we don’t want the tags to get mixed up, it’s better to create a separate logger. Among other things, it will also be optimal in terms of performance 🙂

The second thing we do is redefine setupDI method for actually deserializing the config and passing it to Koin

override fun Module.setupDI(database: Database, params: JsonObject) {
    single { /*1*/get<Json>()./*4*/decodeFromJsonElement(/*3*/Config.serializer(), /*2*/params["introduction"] ?: return@single null) }

To begin with, we receive three parameters at the setupDI input:

  • Module is a receiver (receiver) function and is available within its framework as this

  • database: Database – in fact, Exposed Database, which can be used to save data (but we will not do this in this part)

  • params: JsonObject – PlaguBot’s configuration as it was decoded from the configuration file

And now line by line Let’s see what’s going on here:

  1. get<Json>() – get an object of type Json for further deserialization of the config

  2. params["introduction"] ?: return@single null – take an element from the PlaguBot config by key introductionand if there is none, we return null (that is, you cannot create a config)

  3. Config.serializer() – we get the config serializer (it is available thanks to the annotation @Serializable)

  4. decodeFromJsonElement(сериализатор, наш элемент из конфига) – in fact, we turn the element from the config into the configuration object of our plugin

Actually, the call single say Koin call this code once and save its result.

Wow, why is it so difficult/simple?

In the introduction, I tried to describe in as much detail as possible all the important points that came to mind, so on the one hand there is a lot of information, but on the other, it is all very boring 😔

Well, the third, most difficult part is setting up the bot. Actually, the code is:

override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
    val config = koin.getOrNull<Config>()

    if (config == null) {
        log.w("Plugin has been disabled due to absence of \"introduction\" field in config or some error during configuration loading")

    onCommand("start", initialFilter = { is PrivateChat }) {

Well, let’s go here line by line

  • (1) We get two parameters as input: BehaviourContext, which combines the context for asynchrony, the bot, and information about updates coming to the bot; and koin: Koin – our source of dependencies and parameters, including those we registered with setupDI

  • (2) val config = koin.getOrNull<Config>() – getting the current config of our plugin, or null – the very absence of a config if the field introduction was omitted in json config

  • (4 – 7) If the config was not received – we swear about it in the console and finish the plugin setup

  • (9) – onCommand("start", initialFilter = { is PrivateChat }) – configuration for waiting for the start command. In addition, filtering is used here – the block, which will be described below, will be called only for private messages. You can read more here

  • sendMessage(, config.onStartCommandMessage) – sending messages to the chat in which we received the start command. Here it is commonmessage<text content> – that is, a normal message with text

Here, in general, and all 😊


In this tutorial we:

  • Figured out how to run bots and configure them

  • Created a plugin for PlaguBot

  • Learned how to respond to users’ commands /start

Among other things, we figured out a little about the terminology and structure of bots as part of this series of tutorials. To create your own bot from scratch, you can use this template and if you want to use tgbotapi without any extra bindings like Koin/PlaguBot/Exposed – you can take advantage of this template.

Thank you for your attention to the article and I will be glad to comments / comments / suggestions 😊

Similar Posts

Leave a Reply