Telegram messenger channel

Allows to create chatbots for Telegram.

Built on top of Kotlin Telegram Bot library.

How to use

1. Include Telegram dependency to your build.gradle

implementation("com.just-ai.jaicf:telegram:$jaicfVersion")

Replace $jaicfVersion with the latest version

Also add Jitpack to repositories:

repositories {
    mavenCentral()
    jcenter()
    maven(uri("https://jitpack.io"))
}

2. Use Telegram request and reactions in your scenarios’ actions

action {
    // Telegram incoming message
    val message = request.telegram?.message

    // Fetch username
    val username = message?.chat?.username
    
    // Use Telegram-specified response builders
    reactions.telegram?.say("Are you agree?", listOf("Yes", "No"))
    reactions.telegram?.image("https://address.com/image.jpg", "Image caption")
    reactions.telegram?.api?.sendAudio(message?.chat?.id, File("audio.mp3"))

    // Or use standard response builders
    reactions.say("Hello there!")
    reactions.image("https://address.com/image.jpg")
}

Note that Telegram bot works as long polling. This means that every reactions’ method actually sends a response to the user.

Refer to the TelegramReactions class to learn more about available response builders.

Native API

You can use native Telegram API directly via reactions.telegram?.api. This enables you to build any response that Telegram supports using channel-specific features. As well as fetch some data from Telegram bot API (like getMe for example).

action {
    val me = reactions.telegram?.run {
        api.getMe().first?.body()?.result
    }
}

Learn more about available API methods here.

3. Create a new bot in Telegram

Create a new bot using Telegram’s @BotFather and any Telegram client as described here. Copy your new bot’s access token to the clipboard.

4. Create and run Telegram channel

Using JAICP

For local development:

fun main() {
    JaicpPollingConnector(
        botApi = helloWorldBot,
        accessToken = "your JAICF project token",
        channels = listOf(
            TelegramChannel
        )
    ).runBlocking()
}

For cloud production:

fun main() {
    JaicpServer(
        botApi = helloWorldBot,
        accessToken = "your JAICF project token",
        channels = listOf(
            TelegramChannel
        )
    ).start(wait = true)
}

Or locally:

fun main() {
    TelegramChannel(helloWorldBot, "access token").run()
}

Commands

Telegram enables users not only to send a text queries or use buttons. It also provides an ability to send commands that start from slash.

The most known command of the Telegram is “/start” that is sending once the user starts using your chatbot. Your scenario must handle this command via regex activator to react on the first user’s request.

val HelloWorldScenario = Scenario {
    state("main") {
        activators {
            regex("/start")
        }

        action {
            reactions.say("Hello there!")
        }
    }
}

To make it work, just add RegexActivator to the array of activators in your agent’s configuration:

val helloWorldBot = BotEngine(
    scenario = HelloWorldScenario,
    activators = arrayOf(
        RegexActivator,
        CatchAllActivator
    )
)

The same way you can react on ony other Telegram commands.

Events

User can send not only a text queries to your Telegram bot. They can also send contacts and locations for example. These messages contain non-text queries and can be handled in your scenarios via event activators.

state("events") {
    activators {
        event(TelegramEvent.LOCATION)
        event(TelegramEvent.CONTACT)
    }

    action {
        val location = request.telegram?.location
        val contact = request.telegram?.contact
    }
}

Buttons

Telegram allows to add keyboard or inline keyboard to the text message reply. This means that it’s not possible to add a keyboard without an actual text response.

action {
    reactions.say("Click on the button below")
    reactions.buttons("Click me", "Or me")
}

This code generates inline keyboard right below the text “Click on the button below”. Once the user clicks on any of these buttons, the title of the clicked one returns to the bot as a new query.

To add any keyboard to the response, you can use a channel-specific methods:

action {
    // Append inline keyboard
    reactions.telegram?.say("Are you agree?", listOf("Yes", "No"))

    // Append arbitrary keyboard layout
    reactions.telegram?.say(
        "Could you please send me your contact?", 
        replyMarkup = KeyboardReplyMarkup(
            listOf(listOf(KeyboardButton("Send", requestContact = true), KeyboardButton("No")))
        )
    )
}

You can also remove keyboard sending a ReplyKeyboardRemove in the response:

action {
    reactions.telegram?.say("Okay then!", replyMarkup = ReplyKeyboardRemove())
}

Refer to the TelegramReactions class to learn more about buttons replies.

Payments

You can accept payments for services or goods you provide from Telegram users. To do this, you need to connect a payment system and obtain its unique token.

action {
    val info = PaymentInvoiceInfo(
        "title",
        "description",
        "unique payload",
        "381964478:TEST:67912",
        "unique-start-parameter",
        "USD",
        listOf(LabeledPrice("price", BigInteger.valueOf(20_00)))
    )
    reactions.telegram?.sendInvoice(info)
}

To learn about available currencies and more, you can read the telegram payment documentation.

Goods availability

Before proceeding with the payment, Telegram sends a request to bot to check the goods availability. This request triggers preCheckout event in scenario. Add the preCheckout as a top level state.

Note that when paying in group chats, payment confirmation is sent to the user who sent the payment request, not the entire chat. So there will be created a separate context for the user. If the user communicates with the user in a personal chat the context remains the same.

state("preCheckout") {
    activators {
        event(TelegramEvent.PRE_CHECKOUT)
    }

    action(telegram.preCheckout) {
        reactions.answerPreCheckoutQuery(request.preCheckoutQuery.id, true)
    }
}

You always need to handle the telegramPreCheckout event in the script. Otherwise payments will fail, and all subsequent user messages will be handled in the CatchAll state.

Also you can handle successfulPayment event inside nested states in the TelegramPayment state

state("successfulPayment") {
    activators {
        event(TelegramEvent.SUCCESSFUL_PAYMENT)
    }

    action(telegram.successfulPayment) {
        reactions.say("We are glad you bought from us")
    }
}