/telekt

Simple asynchronous wrapper for telegram bot api, written in pure kotlin.

Primary LanguageKotlinMIT LicenseMIT

TeleKt

Easy to use, asynchronous wrapper for the Telegram Bot API (support v4.3) written in pure Kotlin. Inspired by aiogram.

logo

Table of content:

Getting started.

Install to your project:

  • Using Gradle.kts:
implementation("rocks.waffle.telekt:telekt:0.6.7")

Also this project uses kotlinx.serialization, so you need to add kotlinx repository:

repositories {
    maven("https://kotlin.bintray.com/kotlinx")
}

Note: While API is ready-to-use, it is still in beta. Any new version can break backward compatibility, see change logs and be careful.

Writing your first bot

Prerequisites

It is presumed that you have obtained an API token with @BotFather. We will call this token TOKEN. Furthermore, you have basic knowledge of the Kotlin programming language and more importantly the Telegram Bot API.

A simple echo bot

TeleKt splits calling tg api methods and dispatching incoming updates.

  • For first feature there is class Bot that encapsulates all API calls in a single class.
  • For second — class Dispatcher provide several ways to listen for incoming updates (e.g. messages).

Create a file called echobot.kt. Then, open the file and create an instance of the Bot and Dispatcher classes.

import rocks.waffle.telekt.bot.*
import rocks.waffle.telekt.dispatcher.*

fun main() {
    val bot = Bot("TOKEN")
    val dp = Dispatcher(bot)
}

Note: Make sure to actually replace TOKEN with your own API token.

After that declaration, we need to register some so-called message handlers. Message handlers define filters which a message must pass. If a message passes the filter, the passed function is called and the incoming message is passed as an argument.

Let's define a message handler which handles incoming /start and /help commands.

dp.messageHandler(CommandFilter("start", "help")) { message: Message ->
    bot.answerOn(message, "Howdy, how are you doing?")
}

Let's add another handler:

dp.messageHandler { message ->
    bot.answerOn(message, message.text ?: "this message has no text")
}

This one echoes all incoming text messages back to the sender. We doesn't pass any filters, so all messages will income here.
Note: all handlers are tested in the order in which they were declared

We now have a basic bot which replies a static message to "/start" and "/help" commands and which echoes the rest of the sent messages. To start the bot, add the following to our source file:

dp.poll()

Alright, that's it! Our source file now looks like this:

import rocks.waffle.telekt.bot.*
import rocks.waffle.telekt.dispatcher.*
import rocks.waffle.telekt.types.Message
import rocks.waffle.telekt.contrib.filters.CommandFilter


suspend fun main() {
// ^^^^ NOTE: this lib is async, so you need to run it from suspending funciton
    val bot = Bot("TOKEM")
    val dp = Dispatcher(bot)

    dp.messageHandler(CommandFilter("start", "help")) { message: Message ->
        bot.answerOn(message, "Hi there 0/")
    }

    dp.messageHandler { message ->
        bot.answerOn(message, message.text ?: "this message has no text")
    }

    dp.poll()
}

After running bot, test it by sending commands ('/start' and '/help') and arbitrary text messages.
Full example you can see at there

General API Documentation

Types

All types are defined in telekt.types package.
They are all completely in line with the Telegram API's definition of the types, except that all field renamed from snake_case to camelCase (like message.message_id => message.messageId). Thus, attributes such as messageId can be accessed directly with message.messageId.

The Message class also has a contentType attribute, which defines the type of the Message.

Methods

All API methods are located in the Bot class.

General use of the API

Outlined below are some general use cases of the API.

Message handlers

A message handler is a function that is given to messageHandler function of a Dispatcher instance.
Message handlers consist of 0, one or multiple filters. Each filter's test(...) function must return True for a certain message in order for a message handler to become eligible to handle that message. A message handler is declared in the following way:

dp.messageHandler(*filters) { /* ... */ }

fun functionName(message: Message) { /* ... */ }

dp.messageHandler(*filters, block = ::functionName)

functionName is not bound to any restrictions. Any function name is permitted with message handlers. The function must accept at most one argument, which will be the message event that the function must handle. filters is a vararg array of Filters. One handler may have multiple filters.

There is also DSL-like builder for registration handlers:

dp.dispatch {
    messages {
        handle(/* filters here */) { /* message handler here */ }
    }
    
    callbackQuerys { 
        handle(/* filters here */) { /* callback query handler here */ }
    }
    
    // and so on
}

Important: all handlers are tested in the order in which they were declared

All other handlers (callbackQuery, editedMessage, etc) work the same way.

Bot.me

Bot.me is lazy started coroutine builded with async{} builder that just calls bot.getMe(). Only bot creator can change bot user (via BotFather) so in most cases it's safe to use it.

bot.me.await()

Reply markup

All send<Something> functions of Bot take an optional replyMarkup argument.
This argument must be an instance of ReplyKeyboardMarkup, InlineKeyboardMarkup, ReplyKeyboardRemove or ForceReply, which are defined in markup.kt.

Inline Mode

More information about Inline mode.

Refer Bot Api for extra details

Advanced use of the API

// TODO: write about

  • dispatch modes (need to be implemented)
  • FSM
  • HandlerScope, HandlerContext

Sending large text messages

Sometimes you must send messages that exceed 5000 characters. The Telegram API can not handle that many characters in one request, so we need to split the message in multiples. Here is how to do that using the API:

import rocks.waffle.telekt.util.splitByLenght

val largeText = "Really large text here"

// Split the text each 3000 characters.
// splitByLenght returns a list with the splitted text.
val texts = largeText.splitByLenght() // you can also pass different max lenght
texts.forEach { bot.sendMessage(chat_id, it) }

Using web hooks

// TODO

Logging

This lib is using KotlinLogging. In examples we are using logback.

F.A.Q.

No questions there yet, ask me something! :)

The Telegram Chat Group(s) (and channel)

Get help. Discuss. Chat.

Examples

All examples are located in examples directory

Bots using this API

No one yet, you can become first! Send a telegram message to @wafflelapkin, or send an email to waffle.lapkin@gmail.com or write in our group, or open an issue on github.

Wrapping Notes

Note some things about tg bot api wrapping:

  1. All methods that return True in tg bot api (like unbanChatMember), in TeleKt return Unit
  2. All edit* methods splited in 2 overloads:
    1. For messages sent by the bot (return Message)
    2. For inline messages (return Unit)
  3. All names have been changed to match with coding conventions
  4. Some methods that accept const strings in tg bot api (like sendChatAction), in TeleKt accept enums
  5. All chatId params is of type Recipient cause Kotlin haven't algebraic types.

TODO

Things between 'now' and 'release 1.0'

  • Webhooks
    • Receiving updates
    • Answer into webhook
    • Add option to get current webhook state (Running, Stopped, Closed) (?)
  • Middlewares (like in aiogram)
    • Implement middlewares
    • Write some built-in middlewares
      • timeit
      • logging
      • i18n (?)
  • Add more examples
  • Docs
  • Write more comments in code
    • Write comments in telegram types
    • Write comments everywhere else
  • Add more storages
    • Mongo db
    • PostrgeSQL
  • Safe update dispatching (with different Dispatcher?)
  • Anti-spam and/or api request limits handling

Special thanks to

  • gt22 and mrAppleXZ from pearx team — for answering my stupid questions
  • Russian Kotlin Community in telegram — also for answering my stupid questions
  • Alex Root Junior — for writing aiogram
  • Dmitriy Shilnikov — for fixing jackson deserialization (Actually now TeleKt use kotlinx.serialization instead of jackson, but anyway thanks)
  • afdw — for cleaning logo.svg
  • My parents — for love and support ❤️