telemux

package module
v1.9.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 25, 2021 License: MIT Imports: 11 Imported by: 1

README

telemux

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

GitHub tag Go Reference Build Status Maintainability Test Coverage 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

  • Extension for go-telegram-bot-api library, meaning you'll still use all of its features
  • Designed with statelessness in mind
  • Extensible handler configuration inspired by python-telegram-bot library
  • Conversations (aka Dialogs) based on finite-state machines (see ./examples/album_conversation/main.go)
  • Pluggable persistence for conversations. E. g. you can use database to store the states & intermediate values of conversations (see ./examples/album_conversation/main.go and ./persistence.go)
  • Support for GORM as a persistence backend via gormpersistence module
  • Flexible handler filtering. E. g. And(Or(HasText(), HasPhoto()), IsPrivate()) will only accept direct messages containing photo or text (see ./filters.go)

Minimal example

package main

import (
    tm "github.com/and3rson/telemux"
    tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
    "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.NewCommandHandler(
            "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(bot, 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(bot, update)
}

You can also nest Mux instances:

// See "Handlers & filters" section below for more info on filters.
mux_a := tm.NewMux().
    SetGlobalFilter(tm.IsPrivate()).
    AddHandler(/* ... */).
    AddHandler(/* ... */)
mux_b := tm.NewMux().
    SetGlobalFilter(tm.IsGroupOrSuperGroup()).
    AddHandler(/* ... */).
    AddHandler(/* ... */)
mux = tm.NewMux().
    AddMux(mux_a).
    AddMux(mux_b).
    AddHandler(/* ... */)

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 also be applied to the Mux instance using mux.SetGlobalFilter(filter). Such filters will be called for every update before any other filters.

Generic handlers can be created with tm.NewHandler function, however there are shortcuts for adding update-type-specific handlers:

tm.NewMessageHandler(tm.HasPhoto(), func(u *tm.Update) { /* ... */ })
# ...equals to: tm.NewHandler(tm.And(tm.IsMessage(), tm.HasPhoto()), func(u *tm.Update) { /* ... */ })

tm.NewCommandHandler("start", tm.IsPrivate(), func(u *tm.Update) { /* ... */ })
# ...equals to: tm.NewHandler(tm.And(tm.IsCommandMessage("start"), tm.IsPrivate()), func(u *tm.Update) { /* ... */ })

tm.NewCallbackQueryHandler(nil, func(u *tm.Update) { /* ... */ })
# ...equals to: tm.NewHandler(tm.IsCallbackQuery(), func(u *tm.Update) { /* ... */ })

# etc.
Combining filters

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) { /* ... */ },
))
Reusable handler functions

mux.NewHandler can accept more than one handler function. They are all executed sequentially. The chain can be interrupted by any of them by calling u.Consume().

Here is an example:

mux.AddHandler(tm.NewHandler(
    tm.IsCommandMessage("do_work"),
    func(u *tm.Update) {
        // Perform necessary check
        if u.EffectiveUser().ID != 3442691337 { // Boilerplate code that will be copy-pasted way too much
            u.Bot.Send(tgbotapi.Message(u.EffectiveChat().ID, "You are not allowed to ask me to work!"))
            // Stop handling
            return
        }
        // Perform another check
        if !u.EffectiveChat().IsPrivate() { // Another boilerplate code
            u.Bot.Send(tgbotapi.Message(u.EffectiveChat().ID, "I do not accept commands in group chats. Send me a PM."))
            // Stop handling
            return
        }
        // All checks passed, do some actual work
        // ...
    },
))

To avoid repeating boilerplate checks like if user is not "3442691337" then send error and stop, you can move them to separate functions.

The above code can be rewritten as follows:

// CheckAdmin is a reusable handler that not only checks for user's ID but marks update as processed as well
func CheckAdmin(u *tm.Update) {
    if u.EffectiveUser().ID != 3442691337 {
        u.Bot.Send(tgbotapi.Message(u.EffectiveChat().ID, "You are not allowed to ask me to work!"))
        u.Consume() // Mark update as consumed. Following handler functions will not be called.
    }
}

// CheckPrivate is a reusable handler that not only checks for private chat but marks update as processed as well
func CheckPrivate(u *tm.Update) {
    if !u.EffectiveChat().IsPrivate() {
        u.Bot.Send(tgbotapi.Message(u.EffectiveChat().ID, "I do not accept commands in group chats. Send me a PM."))
        u.Consume() // Mark update as consumed. Following handler functions will not be called.
    }
}

// ...

mux.AddHandler(tm.NewHandler(
    tm.IsCommandMessage("do_work"),
    CheckAdmin,
    CheckPrivate,
    func(u *tm.Update) {
        // Do actual work
    },
))
mux.AddHandler(tm.NewHandler(
    tm.IsCommandMessage("other_command")),
    func(u *tm.Update) {
        // This handler will not fire if one of previous handlers (CheckAdmin or CheckPrivate) consumed the update.
    },
))

You can implement some more complex handler functions, for example, a parametrized one:

// CheckUserID checks for specific user ID and marks update processed if check fails
func RequireUserID(userID int) tm.HandleFunc {
    return func(u *tm.Update) {
        if u.EffectuveUser().ID != userID {
            u.Bot.Send(tgbotapi.NewMessage(u.EffectiveChat().ID, "Sorry, I don't know you!"))
            u.Consume()
        }
    }
}

// ...

mux.AddHandler(tm.NewHandler(
    tm.IsCommandMessage("do_work"),
    RequireUserID(3442691337),
    func(u *tm.Update) {
        // Do actual work
    },
))

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.

    Telemux also supports GORM persistence. If you use GORM, you can store conversation states & data in your database by using GORMPersistence from a gormpersistence module.

  • states StateMap - defines what handlers to use in which state.

    States are usually strings like "upload_photo", "send_confirmation", "wait_for_text" and describe the "step" the user is currently at. Empty string ("") should be used as an initial/final state (i. e. if the conversation has not started yet or has already finished.)

    For each state you must provide a slice with at least one Handler. 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 Handler must call u.PersistenceContext.SetState("STATE_NAME") replacing STATE_NAME with the name of the state you want to switch into.

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

  • defaults []*Handler - 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 SetRecover:

mux := tm.NewMux()
# ...
mux.SetRecover(func(u *tm.Update, err error, stackTrace string) {
    fmt.Printf("An error occurred: %s\n\nStack trace:\n%s", err, stackTrace)
})

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 and include Bot instance with every update.

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

Documentation

Overview

Package telemux is a flexible message router add-on for "go-telegram-bot-api".

Make sure to check "go-telegram-bot-api" documentation first: https://github.com/go-telegram-bot-api/telegram-bot-api

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ConversationPersistence

type ConversationPersistence interface {
	// GetState & SetState tell conversation handlers how to retrieve & set conversation state.
	GetState(pk PersistenceKey) string
	SetState(pk PersistenceKey, state string)
	// GetConversationData & SetConversationData allow conversation handlers to store intermediate data.
	GetData(pk PersistenceKey) Data
	SetData(pk PersistenceKey, data Data)
}

ConversationPersistence 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.

type Data

type Data = map[string]interface{}

Data is an alias for map[string]interface{}.

type FilePersistence added in v1.1.5

type FilePersistence struct {
	Filename string
	// contains filtered or unexported fields
}

FilePersistence is an implementation of Persistence. It stores conversation states & conversation data in file.

func NewFilePersistence added in v1.1.5

func NewFilePersistence(filename string) *FilePersistence

NewFilePersistence creates new instance of FilePersistence.

func (*FilePersistence) GetData added in v1.3.0

func (p *FilePersistence) GetData(pk PersistenceKey) Data

GetData reads conversation data from file

func (*FilePersistence) GetState added in v1.1.5

func (p *FilePersistence) GetState(pk PersistenceKey) string

GetState reads conversation state from file

func (*FilePersistence) SetData added in v1.3.0

func (p *FilePersistence) SetData(pk PersistenceKey, data Data)

SetData writes conversation data to file

func (*FilePersistence) SetState added in v1.1.5

func (p *FilePersistence) SetState(pk PersistenceKey, state string)

SetState writes conversation state to file

type FilterFunc added in v1.6.0

type FilterFunc func(u *Update) bool

FilterFunc is used to check if this update should be processed by handler.

func And

func And(filters ...FilterFunc) FilterFunc

And filters updates that pass ALL of the provided filters.

func Any

func Any() FilterFunc

Any tells handler to process all updates.

func HasAnimation added in v1.1.4

func HasAnimation() FilterFunc

HasAnimation filters updates that contain an animation.

func HasAudio added in v1.1.4

func HasAudio() FilterFunc

HasAudio filters updates that contain an audio.

func HasContact added in v1.1.4

func HasContact() FilterFunc

HasContact filters updates that contain a contact.

func HasDocument added in v1.1.4

func HasDocument() FilterFunc

HasDocument filters updates that contain a document.

func HasLocation added in v1.1.4

func HasLocation() FilterFunc

HasLocation filters updates that contain a location.

func HasPhoto added in v1.1.4

func HasPhoto() FilterFunc

HasPhoto filters updates that contain a photo.

func HasRegex added in v1.1.4

func HasRegex(pattern string) FilterFunc

HasRegex filters updates that match a regular expression. For example, HasRegex("^/get_(\d+)$") will handle commands like "/get_42".

func HasSticker added in v1.1.4

func HasSticker() FilterFunc

HasSticker filters updates that contain a sticker.

func HasText added in v1.1.4

func HasText() FilterFunc

HasText filters updates that look like text, i. e. have some text and do not start with a slash ("/").

func HasVenue added in v1.1.4

func HasVenue() FilterFunc

HasVenue filters updates that contain a venue.

func HasVideo added in v1.1.4

func HasVideo() FilterFunc

HasVideo filters updates that contain a video.

func HasVideoNote added in v1.1.4

func HasVideoNote() FilterFunc

HasVideoNote filters updates that contain a video note.

func HasVoice added in v1.1.4

func HasVoice() FilterFunc

HasVoice filters updates that contain a voice message.

func IsAnyCommandMessage added in v1.1.4

func IsAnyCommandMessage() FilterFunc

IsAnyCommandMessage filters updates that contain a message and look like a command, i. e. have some text and start with a slash ("/"). If command contains bot username, it is also checked.

func IsCallbackQuery added in v1.0.2

func IsCallbackQuery() FilterFunc

IsCallbackQuery filters updates that are callbacks from button presses.

func IsChannel

func IsChannel() FilterFunc

IsChannel filters updates that are sent in channels.

func IsChannelPost added in v1.0.2

func IsChannelPost() FilterFunc

IsChannelPost filters updates that are channel posts.

func IsCommandMessage added in v1.1.4

func IsCommandMessage(cmd string) FilterFunc

IsCommandMessage filters updates that contain a specific command. For example, IsCommandMessage("start") will handle a "/start" command. This will also allow the user to pass arguments, e. g. "/start foo bar". Commands in format "/start@bot_name" and "/start@bot_name foo bar" are also supported. If command contains bot username, it is also checked.

func IsEditedChannelPost added in v1.0.2

func IsEditedChannelPost() FilterFunc

IsEditedChannelPost filters updates that are edits to existing channel posts.

func IsEditedMessage added in v1.0.2

func IsEditedMessage() FilterFunc

IsEditedMessage filters updates that are edits to existing messages.

func IsGroup

func IsGroup() FilterFunc

IsGroup filters updates that are sent in a group. See also IsGroupOrSuperGroup.

func IsGroupOrSuperGroup

func IsGroupOrSuperGroup() FilterFunc

IsGroupOrSuperGroup filters updates that are sent in both groups and supergroups.

func IsInlineQuery added in v1.0.2

func IsInlineQuery() FilterFunc

IsInlineQuery filters updates that are callbacks from inline queries.

func IsLeftChatMember added in v1.1.1

func IsLeftChatMember() FilterFunc

IsLeftChatMember filters updates that have user in LeftChatMember property.

func IsMessage added in v1.0.3

func IsMessage() FilterFunc

IsMessage filters updates that look like message (text, photo, location etc.)

func IsNewChatMembers added in v1.1.1

func IsNewChatMembers() FilterFunc

IsNewChatMembers filters updates that have users in NewChatMembers property.

func IsPrivate

func IsPrivate() FilterFunc

IsPrivate filters updates that are sent in private chats.

func IsSuperGroup

func IsSuperGroup() FilterFunc

IsSuperGroup filters updates that are sent in a superbroup. See also IsGroupOrSuperGroup.

func Not

func Not(filter FilterFunc) FilterFunc

Not filters updates that do not pass the provided filter.

func Or

func Or(filters ...FilterFunc) FilterFunc

Or filters updates that pass ANY of the provided filters.

type HandleFunc added in v1.6.0

type HandleFunc func(u *Update)

HandleFunc processes update.

type Handler

type Handler struct {
	Filter  FilterFunc
	Handles []HandleFunc
}

Handler defines a function that will handle updates that pass the filtering.

func NewCallbackQueryHandler added in v1.8.0

func NewCallbackQueryHandler(pattern string, filter FilterFunc, handles ...HandleFunc) *Handler

NewCallbackQueryHandler creates a handler for updates that contain callback query which matches the pattern as regexp.

func NewChannelPostHandler added in v1.8.0

func NewChannelPostHandler(filter FilterFunc, handles ...HandleFunc) *Handler

NewChannelPostHandler creates a handler for updates that contain channel post.

func NewCommandHandler added in v1.8.0

func NewCommandHandler(command string, filter FilterFunc, handles ...HandleFunc) *Handler

NewCommandHandler is an extension for NewMessageHandler that creates a handler for updates that contain message with command. It also populates u.Context["args"] with a slice of strings.

For example, when invoked as `/somecmd foo bar 1337`, u.Context["args"] will be set to []string{"foo", "bar", "1337"}

command can be a string (like "start" or "somecmd") or a space-delimited list of commands to accept (like "start somecmd othercmd")

Example
package main

import (
	"fmt"
	"os"
	"strconv"

	tm "github.com/and3rson/telemux"
	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
)

func main() {
	bot, _ := tgbotapi.NewBotAPI(os.Getenv("TG_TOKEN"))
	u := tgbotapi.NewUpdate(0)
	updates, _ := bot.GetUpdatesChan(u)
	mux := tm.NewMux()
	mux.AddHandler(tm.NewCommandHandler(
		"add",
		nil,
		func(u *tm.Update) {
			args := u.Context["args"].([]string)
			if len(args) != 2 {
				bot.Send(tgbotapi.NewMessage(
					u.EffectiveChat().ID, "Wrong number of arguments. Example: /add 13 37"),
				)
				return
			}
			a, err1 := strconv.Atoi(args[0])
			b, err2 := strconv.Atoi(args[1])
			if err1 != nil || err2 != nil {
				bot.Send(tgbotapi.NewMessage(
					u.EffectiveChat().ID, "Arguments must be numbers. Example: /add 13 37"),
				)
				return
			}
			bot.Send(tgbotapi.NewMessage(
				u.EffectiveChat().ID, fmt.Sprintf("%d + %d = %d", a, b, a+b),
			))
		},
	))
	for update := range updates {
		mux.Dispatch(bot, update)
	}
}
Output:

func NewConversationHandler added in v1.0.1

func NewConversationHandler(
	conversationID string,
	persistence ConversationPersistence,
	states StateMap,
	defaults []*Handler,
) *Handler

NewConversationHandler creates a conversation handler.

"conversationID" 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" 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.

"states" define what handlers to use in which state. States are usually strings like "upload_photo", "send_confirmation", "wait_for_text" and describe the "step" the user is currently at. Empty string (`""`) should be used as an initial/final state (i. e. if the conversation has not started yet or has already finished.) For each state you must provide a slice with at least one Handler. 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 Handler must call `u.PersistenceContext.SetState("STATE_NAME") ` replacing STATE_NAME with the name of the state you want to switch into. Conversation data can be accessed with `u.PersistenceContext.GetData()` and updated with `u.PersistenceContext.SetData(newData)`.

"defaults" are "appended" to every state except default state (`""`). They are useful to handle commands such as "/cancel" or to display some default message.

func NewEditedChannelPostHandler added in v1.8.0

func NewEditedChannelPostHandler(filter FilterFunc, handles ...HandleFunc) *Handler

NewEditedChannelPostHandler creates a handler for updates that contain edited channel post.

func NewEditedMessageHandler added in v1.8.0

func NewEditedMessageHandler(filter FilterFunc, handles ...HandleFunc) *Handler

NewEditedMessageHandler creates a handler for updates that contain edited message.

func NewHandler added in v1.0.1

func NewHandler(filter FilterFunc, handles ...HandleFunc) *Handler

NewHandler creates a new generic handler.

func NewInlineQueryHandler added in v1.8.0

func NewInlineQueryHandler(pattern string, filter FilterFunc, handles ...HandleFunc) *Handler

NewInlineQueryHandler creates a handler for updates that contain inline query which matches the pattern as regexp.

func NewMessageHandler added in v1.8.0

func NewMessageHandler(filter FilterFunc, handles ...HandleFunc) *Handler

NewMessageHandler creates a handler for updates that contain message.

func NewRegexHandler added in v1.8.12

func NewRegexHandler(pattern string, filter FilterFunc, handles ...HandleFunc) *Handler

NewRegexHandler creates a handler for updates that contain message which matches the pattern as regexp.

func (*Handler) Process added in v1.8.0

func (h *Handler) Process(u *Update) bool

Process runs handler with provided Update.

type LocalPersistence

type LocalPersistence struct {
	States map[PersistenceKey]string
	Data   map[PersistenceKey]Data
}

LocalPersistence is an implementation of Persistence. It stores conversation states & conversation data in memory.

All data in this implementation of persistence is lost if an application is restarted. If you want to store the data permanently you will need to implement your own Persistence which will use redis, database or something else to store states & conversation data.

func NewLocalPersistence

func NewLocalPersistence() *LocalPersistence

NewLocalPersistence creates new instance of LocalPersistence.

func (*LocalPersistence) GetData added in v1.3.0

func (p *LocalPersistence) GetData(pk PersistenceKey) Data

GetData returns conversation data from memory

func (*LocalPersistence) GetState

func (p *LocalPersistence) GetState(pk PersistenceKey) string

GetState returns conversation state from memory

func (*LocalPersistence) SetData added in v1.3.0

func (p *LocalPersistence) SetData(pk PersistenceKey, data Data)

SetData stores conversation data in memory

func (*LocalPersistence) SetState

func (p *LocalPersistence) SetState(pk PersistenceKey, state string)

SetState stores conversation state in memory

type Map added in v1.8.9

type Map = map[string]interface{}

Map is an alias for map[string]interface{}.

type Mux

type Mux struct {
	Processors   []Processor // Contains instances of Mux & Handler
	Recover      RecoverFunc
	GlobalFilter FilterFunc
}

Mux contains handlers, nested multiplexers and global filter.

func NewMux added in v1.0.1

func NewMux() *Mux

NewMux creates new multiplexer.

Example
package main

import (
	"os"

	tm "github.com/and3rson/telemux"
	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
)

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(bot, update)
	}
}
Output:

func (*Mux) AddHandler

func (m *Mux) AddHandler(handlers ...*Handler) *Mux

AddHandler adds one or more handlers to multiplexer. This function returns the receiver for convenient chaining.

func (*Mux) AddMux added in v1.5.1

func (m *Mux) AddMux(others ...*Mux) *Mux

AddMux adds one or more nested multiplexers to this multiplexer. This function returns the receiver for convenient chaining.

func (*Mux) Dispatch

func (m *Mux) Dispatch(bot *tgbotapi.BotAPI, u tgbotapi.Update) bool

Dispatch tells Mux to process the update. Returns true if the update was processed by one of the handlers.

func (*Mux) Process added in v1.8.0

func (m *Mux) Process(u *Update) bool

Process runs mux with provided update.

func (*Mux) SetGlobalFilter added in v1.5.1

func (m *Mux) SetGlobalFilter(filter FilterFunc) *Mux

SetGlobalFilter sets a filter to be called for every update before any other filters. This function returns the receiver for convenient chaining.

func (*Mux) SetRecover added in v1.7.0

func (m *Mux) SetRecover(recover RecoverFunc) *Mux

SetRecover registers a function to call when a panic occurs. This function returns the receiver for convenient chaining.

type PersistenceContext added in v1.3.0

type PersistenceContext struct {
	Persistence ConversationPersistence
	PK          PersistenceKey
	NewState    *string
}

PersistenceContext allows handler to get/set conversation data & change conversation state.

func (*PersistenceContext) ClearData added in v1.3.0

func (c *PersistenceContext) ClearData()

ClearData clears data of current conversation.

func (*PersistenceContext) GetData added in v1.3.0

func (c *PersistenceContext) GetData() Data

GetData returns data of current conversation.

func (*PersistenceContext) PutDataValue added in v1.8.4

func (c *PersistenceContext) PutDataValue(key string, value interface{})

PutDataValue is a shortcut to insert value into conversation data in one line.

func (*PersistenceContext) SetData added in v1.3.0

func (c *PersistenceContext) SetData(data Data)

SetData updates data of current conversation.

func (*PersistenceContext) SetState added in v1.3.0

func (c *PersistenceContext) SetState(state string)

SetState changes state of current conversation.

type PersistenceKey

type PersistenceKey struct {
	ConversationID string `gorm:"primaryKey;autoIncrement:false"`
	UserID         int    `gorm:"primaryKey;autoIncrement:false"` // TODO: Change to int64
	ChatID         int64  `gorm:"primaryKey;autoIncrement:false"`
}

PersistenceKey contains user & chat IDs. It is used to identify conversations with different users in different chats.

func (PersistenceKey) MarshalText added in v1.1.5

func (k PersistenceKey) MarshalText() ([]byte, error)

MarshalText marshals persistence for use in map keys

func (PersistenceKey) String

func (k PersistenceKey) String() string

String returns a string in form "USER_ID:CHAT_ID".

func (*PersistenceKey) UnmarshalText added in v1.1.5

func (k *PersistenceKey) UnmarshalText(b []byte) error

UnmarshalText unmarshals persistence from "CONV:USER:CHAT" string

type Processor added in v1.8.0

type Processor interface {
	Process(u *Update) bool
}

Processor is either Handler or Mux.

type RecoverFunc added in v1.7.0

type RecoverFunc = func(*Update, error, string)

RecoverFunc handles panics which happen during Dispatch.

type StateMap added in v1.8.11

type StateMap map[string][]*Handler

StateMap is an alias to map of strings to handler slices.

type Update

type Update struct {
	tgbotapi.Update
	Bot                *tgbotapi.BotAPI
	Consumed           bool
	PersistenceContext *PersistenceContext
	Context            Map
}

Update wraps tgbotapi.Update and stores some additional data.

func (*Update) Consume added in v1.4.3

func (u *Update) Consume()

Consume marks update as processed. Used by handler functions to interrupt further processing of the update.

func (*Update) EffectiveChat added in v1.0.5

func (u *Update) EffectiveChat() *tgbotapi.Chat

EffectiveChat retrieves chat object from update.

func (*Update) EffectiveMessage added in v1.0.5

func (u *Update) EffectiveMessage() *tgbotapi.Message

EffectiveMessage retrieves message object from update.

func (*Update) EffectiveUser added in v1.0.5

func (u *Update) EffectiveUser() *tgbotapi.User

EffectiveUser retrieves user object from update.

func (*Update) Fields added in v1.8.9

func (u *Update) Fields() Map

Fields returns some metadata of this update. Useful for passing this directly into logrus.WithFields() or other loggers.

Directories

Path Synopsis
examples
album_conversation
album_conversation is a bot that allows users to upload & share photos.
album_conversation is a bot that allows users to upload & share photos.
cat_callback
echo is a bot that repeats whatever you tell him.
echo is a bot that repeats whatever you tell him.
echo
echo is a bot that repeats whatever you tell him.
echo is a bot that repeats whatever you tell him.
error_handling
error_handling is a bot that handles zero division panic.
error_handling is a bot that handles zero division panic.
filters
filters is a bot that can receive photos, text messages & geolocations.
filters is a bot that can receive photos, text messages & geolocations.
members
unknown_group is a bot that will say hello/goodbye when members enver/leave group.
unknown_group is a bot that will say hello/goodbye when members enver/leave group.
nested_mux
nested_mux is a bot that demonstrates nested Mux usage
nested_mux is a bot that demonstrates nested Mux usage
private_only
private_only is a bot that allows you to talk to him only in private chat.
private_only is a bot that allows you to talk to him only in private chat.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL