Skip to content

MediaNik5/spring-boot-starter-telegram-bot

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Spring boot starter for telegram bot

This project is intended to improve your development experience of telegram bots with power of Spring Boot.

This starter lets you write code in controller-like style for handling telegram bot commands.

Table of contents

How to use

You can initialize project with Spring Initialzr with kotlin as language and gradle project

Add this to your dependencies in build.gradle.kts:

dependencies {
    val tgbotapi_version = "2.1.0"
    implementation("dev.inmo:tgbotapi:$tgbotapi_version")
    implementation("dev.inmo:tgbotapi.core:$tgbotapi_version")
    implementation("dev.inmo:tgbotapi.api:$tgbotapi_version")
    implementation("dev.inmo:tgbotapi.utils:$tgbotapi_version")
    implementation("org.springframework.boot:spring-boot-starter")
    implementation("org.springframework.boot:spring-boot-starter-validation")
    implementation("io.github.medianik5:spring-boot-starter-telegram-bot:0.1")
}

And apply few plugins:

plugins {
    id("org.springframework.boot") version "2.7.1"
    id("io.spring.dependency-management") version "1.0.11.RELEASE"
    kotlin("jvm") version "1.6.21"
    kotlin("plugin.spring") version "1.6.21"
}

Or you could use build.gradle:

def tgbotapi_version = "2.1.0"
dependencies {
    implementation "dev.inmo:tgbotapi:${tgbotapi_version}"
    implementation "dev.inmo:tgbotapi.core:${tgbotapi_version}"
    implementation "dev.inmo:tgbotapi.api:${tgbotapi_version}"
    implementation "dev.inmo:tgbotapi.utils:${tgbotapi_version}"
    implementation "org.springframework.boot:spring-boot-starter"
    implementation "org.springframework.boot:spring-boot-starter-validation"
    implementation "io.github.medianik5:spring-boot-starter-telegram-bot:0.1"
}

And apply few plugins:

plugins {
    id "org.springframework.boot"  version "2.7.1"
    id "io.spring.dependency-management"  version "1.0.11.RELEASE"
    kotlin "jvm"  version "1.6.21"
    kotlin "plugin.spring"  version "1.6.21"
}

Create application.properties in resources folder and fill bot token and bot username, get them from BotFather:

io.github.medianik.telegram.bot-token=
io.github.medianik.telegram.bot-username=

Create main class in non-default package:

package com.example.bot

@SpringBootApplication
class DemoApplication

fun main(args: Array<String>){
  runApplication<DemoApplication>(*args)
}

Principles

Controller-likeness

Every controller can be bot command handler. For this purpose, you need to mark your @Controller class with @BotHandler:

import io.github.medianik.starter.telegram.annotation.BotHandler
import org.springframework.stereotype.Controller

@Controller
@BotHandler
class SimpleBotController {
    // ...
}

Every command should be annotated with @BotCommand
It must have at least one property command for command value. Command might consist only of english letters and underscores. Notice: You cannot have two handlers handling same command.

...
import io.github.medianik.starter.telegram.annotation.BotCommand

@Controller
@BotHandler
class SimpleBotController {
    @BotCommand("/command")
    fun handleCommand(){
        // when somebody fires "/command" to your bot(or chat with your bot)
        // this function will be called
    }
}

Return value

By default if you have String return value in bot handler, it will be automatically be converted to reply message to user who's send the message to bot.

@BotCommand("/command")
fun handleCommand(): String {
    // ...
    return "Hello, world!" // bot would reply "Hello, world!" to user
}

Annotations

Parameters

Param

If you want to get some params that user has sent to bot alongside with command, you can use @Param annotation:

import io.github.medianik.starter.telegram.annotation.param.Param
...
@BotCommand("/command")
fun handleCommand(@Param name: String){
    // user sent message "/command name"
    // and now you have "name" in this scope
}

You can have multiple @Param annotations for same command.
They will go in sequential order, meaning that first @Param will be resolved with first word after command, second @Param will be resolved with second word after command, and so on.

You may have any other input arguments inbetween ones with @Param annotation, then the order for @Params will be the same as if there were no other arguments.

@BotCommand("/command")
fun handleCommand(@Param name: String, somethingVeryUseful: SomeType, @Param lastName: String){
    // for "/command Joe Baiden"
    // name would be "Joe", lastName would be "Baiden"
}

BotValue

If you want to get instance of bot to do some difficult logic, you can use @BotValue on parameter to get it.
If you specify type for parameter as TelegramBot, it will be resolved as TelegramBot instance automatically.

import dev.inmo.tgbotapi.bot.TelegramBot
...
@BotCommand("/command")
fun handleCommand(bot: TelegramBot){
    // you can use `bot` here...
}

ChatValue

You can get chat instance or chat id for message that was sent to bot.
If you use Chat type, you might not specify the type. If you want to get chat id, you have to use type Long and annotation @ChatValue.

import io.github.medianik.starter.telegram.annotation.param.ChatValue
import dev.inmo.tgbotapi.types.chat.Chat
...
@BotCommand("/command")
fun handleCommand(chat: Chat){
    // you can use `chat` here...
}
@BotCommand("/command1")
fun handleCommand1(@ChatValue chatId: Long){
    // you can use `chatId` here...
}

MessageValue

If you want to get message instance, you can specify type Message for param (@MessageValue is optional).
If you want to get message id, you have to use type Long and annotation @MessageValue.

import io.github.medianik.starter.telegram.annotation.param.MessageValue
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
import dev.inmo.tgbotapi.types.message.content.MessageContent

@BotCommand("/command")
fun handleCommand(message: CommonMessage<out MessageContent>){
    // you can use `message` here...
}

@BotCommand("/command1")
fun handleCommand1(@MessageValue messageId: Long){
    // you can use `messageId` here...
}

SendDateValue

If you want to get DateTime when message was sent, use @SendDateValue annotation. Supported types are: java's Instant, LocalDate, LocalTime, LocalDateTime, and third party's com.soywiz.klock.DateTime

import io.github.medianik.starter.telegram.annotation.param.SendDateValue
import com.soywiz.klock.DateTime
...
@BotCommand("/command")
fun handleCommand(@SendDateValue date: DateTime){
    // you can use `date` here...
}

UserValue

If you want to get User instance, you can specify type User for param (@UserValue is optional).
If you want to get User id, you have to use type Long and annotation @UserValue.

import io.github.medianik.starter.telegram.annotation.param.UserValue
import dev.inmo.tgbotapi.types.chat.User
...
@BotCommand("/command")
fun handleCommand(user: User){
    // you can use `user` here...
}

@BotCommand("/command1")
fun handleCommand1(@UserValue userId: Long){
    // you can use `userId` here...
}

RemainingParams

If you want to get remaining params after those that you have specified before, you can use @RemainingParams annotation.

For example, for input /command param0 param1 param2 param3 and function

@BotCommand("/command")
fun handleCommand(
    @Param param: String, 
    @RemainingParams params: String, 
    @Param param1
){
    // param will be "param0", params will be "param1 param2 param3"
    // and param1 will be "param1", so you can combine them
}

WholeTextValue

Gets whole text that was sent to bot.

For example for input /command param0 param1 param2 param3

@BotCommand("/command")
fun handleCommand(@WholeTextValue text: String){
    // text will be `/command param0 param1 param2 param3`
    // if you want to get only params, use @RemainingParams as first param
}

Other annotations

IgnoreException

Ignore exception if it happens.

For example ignore that there are two handlers for same command.
Notice: in this case any of the handler might be called. The order is not guaranteed.

import io.github.medianik.starter.telegram.annotation.IgnoreException
import io.github.medianik.starter.telegram.exception.DuplicateBotCommandException

@IgnoreException(DuplicateBotCommandException::class)
@BotCommand("/command")
fun handleCommand(){
    // ...
}

@IgnoreException(DuplicateBotCommandException::class)
@BotCommand("/command")
fun handleCommand1(){
    // ...
}

NoReply

If you want to have return type of function to be String, but dont want to reply to user, you can use @NoReply annotation.

import io.github.medianik.starter.telegram.annotation.NoReply
...
@NoReply
@BotCommand("/command")
fun handleCommand(): String{
    return "Ignored String"
}

Default values

You can use power of kotlin and use default values for any argument in your command handler, then if there was not found any appropriate value for that argument, the default value will be used.

@BotCommand("/command")
fun handleCommand(@Param name: String = "Michael", @Param lastName: String = "Obama"){
    // for "/command Joe"
    // name would be "Joe", lastName would be "Obama"
    
    // for "/command"
    // both parameters would be defaulted to "Michael" and "Obama"
}

Examples

Simple command handler

import io.github.medianik.starter.telegram.annotation.BotCommand
import io.github.medianik.starter.telegram.annotation.BotHandler
import io.github.medianik.starter.telegram.annotation.param.Param
import org.springframework.stereotype.Controller

@Controller
@BotHandler
class SimpleBotController {
    /**
     * Command with one optional parameter
     *
     * This method replies to command "/hello [name]" with message "Hello, [name]!"
     * or "Hello, world!" if [name] is not specified by user.
     */
    @BotCommand("/hello", description = "Hellos user or world", example = "/hello [yourName]")
    fun sayHello(@Param name: String = "World"): String{
        return "Hello, $name!"
    }
}

More complex command handler with remembering the users

import io.github.medianik.starter.telegram.annotation.BotCommand
import io.github.medianik.starter.telegram.annotation.BotHandler
import io.github.medianik.starter.telegram.annotation.param.Param
import io.github.medianik.starter.telegram.annotation.param.UserValue
import dev.inmo.tgbotapi.types.chat.User
import org.springframework.stereotype.Controller
import java.util.concurrent.ConcurrentHashMap


@Controller
@BotHandler
class ALittleMoreComplexBotController{
    private val users = ConcurrentHashMap<Long, String>()

    @BotCommand("/start", description = "Adds user to the list of registered users")
    fun start(user: User): String{
        val userName = users.putIfAbsent(user.id.chatId, user.firstName)
        return if(userName == null){
            "Welcome, ${user.firstName}!"
        }else{
            "You are already registered!"
        }
    }

    @BotCommand("/list", description = "Lists all registered users")
    fun list(): String {
        return "Registered users: ${users.values.joinToString(", ")}"
    }

    @BotCommand("/stop", description = "Removes user to the list of registered users")
    fun stop(@UserValue userId: Long): String{
        val user = users.remove(userId)
        return if(user != null){
            "Bye, ${user}!"
        }else{
            "You are not registered!"
        }
    }
}

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published