Flexible message router add-on for go-telegram-bot-api library.

Overview

telemux

Flexible message router add-on for go-telegram-bot-api library.

GitHub tag Go Reference Build Status Maintainability Go Report Card stability-unstable

Screenshot

Table of contents

Motivation

This library serves as an addition to the go-telegram-bot-api library. I strongly recommend you to take a look at it since telemux is mostly an extension to it.

Patterns such as handlers, persistence & filters were inspired by a wonderful python-telegram-bot library.

This project is in early beta stage. Contributions are welcome! Feel free to submit an issue if you have any questions, suggestions or simply want to help.

Features

Minimal example

package main

import (
    tm "github.com/and3rson/telemux"
    tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
    "log"
    "os"
)

func main() {
    // This part is a boilerplate from go-telegram-bot-api library.
    bot, _ := tgbotapi.NewBotAPI(os.Getenv("TG_TOKEN"))
    bot.Debug = true
    u := tgbotapi.NewUpdate(0)
    u.Timeout = 60
    updates, _ := bot.GetUpdatesChan(u)

    // Create a multiplexer with two handlers: one for command and one for all messages.
    // If a handler cannot handle the update (fails the filter),
    // multiplexer will proceed to the next handler.
    mux := tm.NewMux().
        AddHandler(tm.NewHandler(
            tm.IsCommandMessage("start"),
            func(u *tm.Update) {
                bot.Send(tgbotapi.NewMessage(u.Message.Chat.ID, "Hello! Say something. :)"))
            },
        )).
        AddHandler(tm.NewHandler(
            tm.Any(),
            func(u *tm.Update) {
                bot.Send(tgbotapi.NewMessage(u.Message.Chat.ID, "You said: "+u.Message.Text))
            },
        ))
    // Dispatch all telegram updates to multiplexer
    for update := range updates {
        mux.Dispatch(update)
    }
}

Documentation

The documentation is available here.

Examples are available here.

Changelog

Changelog is available here.

Terminology

Mux

Mux (multiplexer) is a "router" for instances of tgbotapi.Update.

It allows you to register handlers and will take care to choose an appropriate handler based on the incoming update.

In order to work, you must dispatch messages (that come from go-telegram-bot-api channel):

mux := tm.NewMux()
// ...
// add handlers to mux here
// ...
updates, _ := bot.GetUpdatesChan(u)
for update := range updates {
    mux.Dispatch(update)
}

Handlers & filters

Handler consists of filter and handle-function.

Handler's filter decides whether this handler can handle the incoming update. If so, handle-function is called. Otherwise multiplexer will proceed to the next handler.

Filters are divided in two groups: content filters (starting with "Has", such as HasPhoto(), HasAudio(), HasSticker() etc) and update type filters (starting with "Is", such as IsEditedMessage(), IsInlineQuery() or IsGroupOrSuperGroup()).

There is also a special filter Any() which makes handler accept all updates.

Filters can be chained using And, Or, and Not meta-filters. For example:

mux := tm.NewMux()

// Add handler that accepts photos sent to the bot in a private chat:
mux.AddHandler(And(tm.IsPrivate(), tm.HasPhoto()), func(u *tm.Update) { /* ... */ })

// Add handler that accepts photos and text messages:
mux.AddHandler(Or(tm.HasText(), tm.HasPhoto()), func(u *tm.Update) { /* ... */ })

// Since filters are plain functions, you can easily implement them yourself.
// Below we add handler that allows onle a specific user to call "/restart" command:
mux.AddHandler(tm.NewHandler(
    tm.And(tm.IsCommandMessage("restart"), func(u *tm.Update) bool {
        return u.Message.From.ID == 3442691337
    }),
    func(u *tm.Update) { /* ... */ },
))

If you want your handler to be executed in a goroutine, use tm.NewAsyncHandler. It's similar to wrapping a handler function in an anonymous goroutine invocation:

mux := tm.NewMux()
// Option 1: using NewAsyncHandler
mux.AddHandler(tm.NewAsyncHandler(
    tm.IsCommandMessage("do_work"),
    func(u *tm.Update) {
        // Slow operation
    },
))
// Option 2: wrapping manually
mus.AddHandler(tm.NewHandler(
    tm.IsCommandMessage("do_work"),
    func(u *tm.Update) {
        go func() {
            // Slow operation
        }
    },
))

Conversations & persistence

Conversations are handlers on steroids based on the finite-state machine pattern.

They allow you to have complex dialog interactions with different handlers.

Persistence interface tells conversation where to store & how to retrieve the current state of the conversation, i. e. which "step" the given user is currently at.

To create a ConversationHandler you need to provide the following:

  • conversationID string - identifier that distinguishes this conversation from the others.

    The main goal of this identifier is to allow persistence to keep track of different conversation states independently without mixing them together.

  • persistence Persistence - defines where to store conversation state & intermediate inputs from the user.

    Without persistence, a conversation would not be able to "remember" what "step" the user is at.

    Persistence is also useful when you want to collect some data from the user step-by-step).

    Two convenient implementations of Persistence are available out of the box: LocalPersistence & FilePersistence.

  • states map[string][]*TransitionHandler - defines which TransitionHandlers to use in what state.

    States are usually strings like "upload_photo", "send_confirmation", "wait_for_text" and describe the "step" the user is currently at. It's recommended to have an empty string ("") as an initial state (i. e. if the conversation has not started yet or has already finished.)

    For each state you can provide a list of at least one TransitionHandler. If none of the handlers can handle the update, the default handlers are attempted (see below).

    In order to switch to a different state your TransitionHandler must call ctx.SetState("STATE_NAME") replacing STATE_NAME with the name of the state you want to switch into.

    Conversation data can be accessed with ctx.GetData() and updated with ctx.SetData(newData).

  • defaults []*TransitionHandler - these handlers are "appended" to every state.

    Useful to handle commands such as "/cancel" or to display some default message.

See ./examples/album_conversation/main.go for a conversation example.

Error handling

By default, panics in handlers are propagated all the way to the top (Dispatch method).

In order to intercept all panics in your handlers globally and handle them gracefully, register your function using SetRecoverer:

mux := tm.NewMux()
# ...
mux.SetRecoverer(func(u *tm.Update, err error) {
    fmt.Printf("An error occured: %s", err)
})

Tips & common pitfalls

tgbotapi.Update vs tm.Update confusion

Since Update struct from go-telegram-bot-api already provides most of the functionality, telemux implements its own Update struct which embeds the Update from go-telegram-bot-api. Main reason for this is to add some extra convenient methods.

Getting user/chat/message object from update

When having handlers for wide filters (e. g. Or(And(HasText(), IsEditedMessage()), IsInlineQuery())) you may often fall in situations when you need to check for multiple user/chat/message attributes. In such situations sender's data may be in one of few places depending on which update has arrived: u.Message.From, u.EditedMessage.From, or u.InlineQuery.From. Similar issue applies to fetching actual chat info or message object from an update.

In such cases it's highly recommended to use functions such as EffectiveChat() (see the update module for more info):

// Bad:
fmt.Println(u.Message.Chat.ID) // u.Message may be nil

// Better, but not so DRY:
chatId int64
if u.Message != nil {
    chatId = u.Message.Chat.ID
} else if u.EditedMessage != nil {
    chatId = u.EditedMessage.Chat.ID
} else if u.CallbackQuery != nil {
    chatId = u.CallbackQuery.Chat.ID
} // And so on... Duh.
fmt.Println(chatId)

// Best:
chat := u.EffectiveChat()
if chat != nil {
    fmt.Println(chat.ID)
}

Properly filtering updates

Keep in mind that using content filters such as HasText(), HasPhoto(), HasLocation(), HasVoice() etc does not guarantee that the Update describes an actual new message. In fact, an Update also happens when a user edits his message! Thus your handler will be executed even if a user just edited one of his messages.

To avoid situations like these, make sure to use filters such as IsMessage(), IsEditedMessage(), IsCallbackQuery() etc in conjunction with content filters. For example:

tm.NewHandler(HasText(), func(u *tm.Update) { /* ... */ }) // Will handle new messages, updated messages, channel posts & channel post edits which contain text
tm.NewHandler(And(IsMessage(), HasText()), func(u *tm.Update) { /* ... */ }) // Will handle new messages that contain text
tm.NewHandler(And(IsEditedMessage(), HasText()), func(u *tm.Update) { /* ... */ }) // Will handle edited that which contain text

The only exceptions are IsCommandMessage("...") and IsAnyCommandMessage() filters. Since it does not make sense to react to edited messages that contain commands, this filter also checks if the update designates a new message and not an edited message, inline query, callback query etc. This means you can safely use IsCommandMessage("my_command") without joining it with the IsMessage() filter:

IsCommandMessage("my_command") // OK: IsCommand() already checks for IsMessage()
And(IsCommandMessage("start"), IsMessage()) // IsMessage() is unnecessary
And(IsCommandMessage("start"), Not(IsEditedMessage())) // Not(IsEditedMessage()) is unnecessary
You might also like...
Golang bindings for the Telegram Bot API

Golang bindings for the Telegram Bot API All methods are fairly self explanatory, and reading the godoc page should explain everything. If something i

Bot that polls activity API for Github organisation and pushes updates to Telegram.

git-telegram-bot Telegram bot for notifying org events Requirements (for building) Go version 1.16.x Setup If you don't have a telegram bot token yet,

WIP Telegram Bot API server in Go

botapi The telegram-bot-api, but in go. WIP. Reference: https://core.telegram.org/bots/api Reference implementation: https://github.com/tdlib/telegram

Golang bindings for the Telegram Bot API

Golang bindings for the Telegram Bot API All methods are fairly self explanatory, and reading the godoc page should explain everything. If something i

UcodeQrTelebot ver2 - Easy way to get QR and U-code using Utopia API in telegram bot
UcodeQrTelebot ver2 - Easy way to get QR and U-code using Utopia API in telegram bot

UcodeQrTelebot Easy way to get QR and U-code using Utopia API in telegram bot Us

The serverless OTP telegram service use telegram as OTP service, and send OTP through webhook

Setup OTP First thing, you need prepare API(webhook) with POST method, the payload format as below { "first_name": "Nolan", "last_name": "Nguyen",

Our library to interact with a telegram bot.

gotelegrambot Here you can find our library for telegram bot's. We develop the API endpoints according to our demand and need. You are welcome to help

A Telegram bot hook for Logrus logging library in Go

logrus2telegram logrus2telegram is a Telegram bot hook for Logrus logging librar

Telegram bot written in Golang using gotgbot library

go_tgbot Telegram bot written in Golang using gotgbot library. How to run go get -u github.com/itsLuuke/go_tgbot rename sample.env to .env and fill in

Comments
  • Add example: NewConversationHandler with CallbackQuery

    Add example: NewConversationHandler with CallbackQuery

    Hi, awesome lib and you've got start from me! But I don't get how to use "ConversationHandler" with callback from inline buttons. Could you add it to examples?

    opened by Vlad-Shevliakov 6
  • Add license scan report and status

    Add license scan report and status

    Your FOSSA integration was successful! Attached in this PR is a badge and license report to track scan status in your README.

    Below are docs for integrating FOSSA license checks into your CI:

    opened by fossabot 1
  • Setting cron job to set state and send message

    Setting cron job to set state and send message

    I would like to set a cron job to send a message and set u.PersistenceContext(). Basically, I would like to send a message to the user, and then set the state for the message to automatically handle the context.

    Is there a simple way to do this?

    opened by vascodegraaff 0
  • New func() IsCallbackCommand

    New func() IsCallbackCommand

    func IsCallbackCommand(cmd string) FilterFunc { return And(IsCallbackQuery(), func(u *Update) bool { matches := commandRegex.FindStringSubmatch(u.CallbackQuery.Data) actualCmd := matches[1] return actualCmd == cmd }) }

    Checking for a specific command in CallBackQuery.

    opened by Plus86S 0
Owner
Andrew Dunai
Codin' around.
Andrew Dunai
Pro-bot - A telegram bot to play around with the community telegram channels

pro-bot ?? Pro Bot A Telegram Bot to Play Around With The Community Telegram Cha

TechProber 1 Jan 24, 2022
Golang telegram bot API wrapper, session-based router and middleware

go-tgbot Pure Golang telegram bot API wrapper generated from swagger definition, session-based routing and middlewares. Usage benefits No need to lear

Oleg Lebedev 118 Nov 16, 2022
A bot based on Telegram Bot API written in Golang allows users to download public Instagram photos, videos, and albums without receiving the user's credentials.

InstagramRobot InstagramRobot is a bot based on Telegram Bot API written in Golang that allows users to download public Instagram photos, videos, and

FTC Team 8 Dec 16, 2021
Telego is Telegram Bot API library for Golang with full API implementation (one-to-one)

Telego • Go Telegram Bot API Telego is Telegram Bot API library for Golang with full API implementation (one-to-one) The goal of this library was to c

Artem Yadelskyi 135 Jan 5, 2023
Bot - Telegram Music Bot in Go

Telegram Music Bot in Go An example bot using gotgcalls. Setup Install the serve

null 9 Jun 28, 2022
Go library for Telegram Bot API

tbot - Telegram Bot Server Features Full Telegram Bot API 4.7 support Zero dependency Type-safe API client with functional options Capture messages by

Alexey Grachov 332 Nov 30, 2022
Library for working with golang telegram client + bot based on tdlib. This library was taken from the user Arman92 and changed for the current version of tdlib.

go-tdlib Golang Telegram TdLib JSON bindings Install To install, you need to run inside a docker container (it is given below) go get -u github.com/ka

Rostislav Krivets 24 Dec 2, 2022
Bot-template - A simple bot template for creating a bot which includes a config, postgresql database

bot-template This is a simple bot template for creating a bot which includes a c

Disgo 2 Sep 9, 2022
its the same idea as bruh-bot, but with golang, and add more bots

bruh-bot but more powerful! requirements python go you can used on mac and linux the idea its really simple, can make a lot of bots with the same task

pai 12 Jul 7, 2021
Client lib for Telegram bot api

Micha Client lib for Telegram bot api. Supports Bot API v2.3.1 (of 4th Dec 2016). Simple echo bot example: package main import ( "log" "git

Andrey 23 Nov 10, 2022