Writing a simple Telegram bot in Kotlin as a Spring Boot application

In this article, I’ll show you how to create a Telegram bot that has some very basic functionality. You may use it as a starting point for your own project. Here is the source code for reference.

Registering a Telegram bot

First of all, write to BotFather. It’s a bot for managing your bots. Yes, there’s a bot for that. Send him “/newbot” command and follow his instructions. As the result, you’ll have your bot’s token. Keep it safe — it’s a key to Telegram Bot API.

Creating a keystore

In order to setup a webhook to our bot, we need to create a keystore. I’ll show you how to create a self-signed certificate if you want to use a verified certificate instead — please go ahead, here is a great guide where you could find an info about webhooks and instructions for trusted certificates.

You’ll need OpenSSL and Java’s keytool installed and added to PATH. Also, you’ll probably need “OPENSSL_CONF” environment variable set with a path to “openssl.cnf” file.

Run following commands. Specify your bot’s domain instead of “your first and last name”. You might want to use a DDNS service if you don’t own a domain. Skip other questions, set key password same as keystore password.

keytool -genkey -keyalg RSA -alias YOUR_DOMAIN -keystore keystore.jks -storepass YOUR_PASSWORD -validity 365 -keysize 2048
keytool -importkeystore -srckeystore keystore.jks -destkeystore keystore.p12 -srcstoretype jks -deststoretype pkcs12
openssl pkcs12 -in keystore.p12 -out public.pem -nokeys

You’ll get keystore.p12 and public.pem, save them for later.

Setting a webhook

We need to tell Telegram servers how our bot could be notified about updates. Telegram bot API documentation suggests us to use our bot’s token as a part of our webhook’s URL. Let’s do it! I use curl for this, please notice that we have to send our public.pem as a file, not as a text. The port you set here could depend on your port forwarding configuration if you’re behind the NAT.

curl -F "url=https://YOUR_DOMAIN:8443/YOUR_TOKEN" -F "certificate=@public.pem" https://api.telegram.org/botYOUR_TOKEN/setWebhook

Preparing the project

Create a “Gradle > Kotlin (Java)” project.

Add the Spring Boot dependencies to your build.gradle. We’ll have also to add kotlin-spring plugin for compatibility.

buildscript {
ext.kotlin_version = '1.1.4-3'
ext.spring_boot_version = '1.5.6.RELEASE'
    repositories {
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"
classpath "org.springframework.boot:spring-boot-gradle-plugin:$spring_boot_version"
}
}
apply plugin: 'kotlin'
apply plugin: 'kotlin-spring'
apply plugin: 'org.springframework.boot'

Check your dependencies, we’ll need a jackson-module-kotlin and some HTTP library. For this example I’ve chosen Unirest.

repositories {
jcenter()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
    compile 'org.springframework.boot:spring-boot-starter-web'
    compile 'com.fasterxml.jackson.module:jackson-module-kotlin:2.9.0'
    compile 'com.mashape.unirest:unirest-java:1.4.9'
}

Create an application.properties file in your project directory.

server.port=8443
server.ssl.keyStore=/path/to/keystore.p12
server.ssl.keyStoreType=PKCS12
server.ssl.keyStorePassword=YOUR_PASSWORD
server.ssl.keyPassword=YOUR_PASSWORD
token=YOUR_BOT_TOKEN

Now create your package and Application.kt file in it. Here we’ll put some configuration for the ObjectMapper and our static “main” function that is needed for Spring Boot:

@SpringBootApplication
class Application
@Configuration
class ObjectMapperConfiguration {
@Bean
@Primary
fun objectMapper() = ObjectMapper().apply {
registerModule(KotlinModule())
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
}
}
fun main(args: Array<String>) {
SpringApplication.run(Application::class.java, *args)
}

Create a model.kt file. It will contain our data classes. The whole model for Bot API is pretty large, but we’ll need only this three classes for our example:

data class Update(
val message: Message?
)
data class Message(
val chat: Chat,
val text: String?
)
data class Chat(
val id: Long
)

Writing the bot controller

OK, now the fun part. Create a BotController class in your package, annotate it with “@RestController annotation. Add a companion objects with constants:

@RestController
class BotController {
    companion object {
private val API_ENDPOINT = "https://api.telegram.org/bot"
        private val START_COMMAND = "/start"
private val ECHO_COMMAND
= "/echo"
}
}

Initialize a logger to help you debug. It’s just an example, you may want to setup a proper logging system in your production build. Add a token field. It is annotated, so Spring Boot will provide its value using your application.property or command line arguments. Notice, how $ is escaped.

private val logger: Logger = Logger.getLogger("[EchoBot]")
@Value("\${token}")
lateinit var token: String

Let’s create the function Telegram will call to notify us about updates. We’ll use “https://YOUR_DOMAIN/YOUR_TOKEN” URL for webhook, so we annotate our function to reflect that. We’re checking the message our bot gets and reacting appropriately.

@PostMapping("/\${token}")
fun onUpdate(@RequestBody update: Update) {
logger.log(Level.INFO, "Got update: " + update)
    if (update.message != null) {
val chatId = update.message.chat.id
val
text = update.message.text
        when {
text?.startsWith(START_COMMAND) == true -> onStartCommand(chatId)
text?.startsWith(ECHO_COMMAND) == true -> onEchoCommand(chatId, text)
}
}
}

On “/start” command we’re just sending a “Hello” message, but potentially here could be some registration logic. On “/echo” command we’re sending user back his text.

private fun onStartCommand(chatId: Long) = try {
sendMessage(chatId, "Hello! I'm EchoBot.")
} catch (e: UnirestException) {
logger.log(Level.SEVERE, "Can not send START response!", e)
}
private fun onEchoCommand(chatId: Long, text: String) = try {
val response = text.subSequence(ECHO_COMMAND.length, text.length).trim().toString()
sendMessage(chatId, response)
} catch (e: UnirestException) {
logger.log(Level.SEVERE, "Can not send ECHO response!", e)
}

Sending text back is very simple. Here we’ll need our bot’s token. API URL for this is “https://api.telegram.org/botYOUR_TOKEN/sendMessage”.

@Throws(UnirestException::class)
private fun sendMessage(chatId: Long, text: String) {
Unirest.post(API_ENDPOINT + token + "/sendMessage")
.field("chat_id", chatId)
.field("text", text)
.asJson()
}

And that’s it. Now you can build your bot and start your Spring Boot application.