Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/NicoNex/echotron

An elegant and concurrent library for the Telegram bot API in Go.
https://github.com/NicoNex/echotron

awesome-go bot go golang golang-library library telegram telegram-api telegram-bot telegram-bot-api

Last synced: 11 days ago
JSON representation

An elegant and concurrent library for the Telegram bot API in Go.

Awesome Lists containing this project

README

        

| logo

[![Language](https://img.shields.io/badge/Language-Go-blue.svg)](https://golang.org/) [![PkgGoDev](https://pkg.go.dev/badge/github.com/NicoNex/echotron/v3)](https://pkg.go.dev/github.com/NicoNex/echotron/v3) [![Go Report Card](https://goreportcard.com/badge/github.com/NicoNex/echotron/v3)](https://goreportcard.com/report/github.com/NicoNex/echotron/v3) [![codecov](https://codecov.io/gh/NicoNex/echotron/graph/badge.svg?token=LVJGOEYL5M)](https://codecov.io/gh/NicoNex/echotron) [![License](http://img.shields.io/badge/license-LGPL3.0-orange.svg?style=flat)](https://github.com/NicoNex/echotron/blob/master/LICENSE) [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go) [![Telegram](https://img.shields.io/badge/Echotron%20News-blue?logo=telegram&style=flat)](https://t.me/echotronnews) |
| :------: |

**Echotron** is an elegant and concurrent library for the Telegram bot API in Go.

Fetch with

```bash
go get github.com/NicoNex/echotron/v3
```

## Example
### Simplest implementations
#### Long polling
```golang
package main

import "github.com/NicoNex/echotron/v3"

const token = "MY TELEGRAM TOKEN"

func main() {
api := echotron.NewAPI(token)

for u := range echotron.PollingUpdates(token) {
if u.Message.Text == "/start" {
api.SendMessage("Hello world", u.ChatID(), nil)
}
}
}
```
#### Webhook
```golang
package main

import "github.com/NicoNex/echotron/v3"

const token = "MY TELEGRAM TOKEN"

func main() {
api := echotron.NewAPI(token)

for u := range echotron.WebhookUpdates("https://example.com:443/my_token", token) {
if u.Message.Text == "/start" {
api.SendMessage("Hello world", u.ChatID(), nil)
}
}
}
```
For more scalable and recommended implementations see the other examples.

### Long Polling

```golang
package main

import (
"log"
"time"

"github.com/NicoNex/echotron/v3"
)

// Struct useful for managing internal states in your bot, but it could be of
// any type such as `type bot int64` if you only need to store the chatID.
type bot struct {
chatID int64
echotron.API
}

const token = "MY TELEGRAM TOKEN"

// This function needs to be of type 'echotron.NewBotFn' and is called by
// the echotron dispatcher upon any new message from a chatID that has never
// interacted with the bot before.
// This means that echotron keeps one instance of the echotron.Bot implementation
// for each chat where the bot is used.
func newBot(chatID int64) echotron.Bot {
return &bot{
chatID,
echotron.NewAPI(token),
}
}

// This method is needed to implement the echotron.Bot interface.
func (b *bot) Update(update *echotron.Update) {
if update.Message.Text == "/start" {
b.SendMessage("Hello world", b.chatID, nil)
}
}

func main() {
// This is the entry point of echotron library.
dsp := echotron.NewDispatcher(token, newBot)
for {
log.Println(dsp.Poll())
// In case of connection issues wait 5 seconds before trying to reconnect.
time.Sleep(5 * time.Second)
}
}
```

## Design

**Echotron** makes a new instance of the struct bot for each open chat with a Telegram user, channel or group.
This allows to:
- safely call the `Update(*echotron.Update)` method concurrently
- give to the user a convenient way to manage the bot internal states across all the chats
- make sure that, even if one instance of the bot is deadlocked, the other ones keep running just fine, making the bot work for other users without any issues and/or slowdowns.

Please note that the the aforementioned behaviour is dictated by the `echotron.Dispatcher` object whose usage is not mandatory and for special needs can be ignored and implemented in different ways still keeping all the methods in the `echotron.API` object.

**Echotron** is designed to be as similar to the official [Telegram API](https://core.telegram.org/bots/api) as possible, but there are some things to take into account before starting to work with this library.

- The methods have the exact same name, but with a capital first letter, since in Go methods have to start with a capital letter to be exported.
_Example: `sendMessage` becomes `SendMessage`_
- The order of the parameters in some methods is different than in the official Telegram API, so refer to the [docs](https://pkg.go.dev/github.com/NicoNex/echotron/v3) for the correct one.
- The only `chat_id` (or, in this case, `chatID`) type supported is `int64`, instead of the "Integer or String" requirement of the official API. That's because numeric IDs can't change in any way, which isn't the case with text-based usernames.
- In some methods, you might find a `InputFile` type parameter. [`InputFile`](https://pkg.go.dev/github.com/NicoNex/echotron/v3#InputFile) is a struct with unexported fields, since only three combination of fields are valid, which can be obtained through the methods [`NewInputFileID`](https://pkg.go.dev/github.com/NicoNex/echotron/v3#NewInputFileID), [`NewInputFilePath`](https://pkg.go.dev/github.com/NicoNex/echotron/v3#NewInputFilePath) and [`NewInputFileBytes`](https://pkg.go.dev/github.com/NicoNex/echotron/v3#NewInputFileBytes).
- In some methods, you might find a `MessageIDOptions` type parameter. [`MessageIDOptions`](https://pkg.go.dev/github.com/NicoNex/echotron/v3#MessageIDOptions) is another struct with unexported fields, since only two combination of field are valid, which can be obtained through the methods [`NewMessageID`](https://pkg.go.dev/github.com/NicoNex/echotron/v3#NewMessageID) and [`NewInlineMessageID`](https://pkg.go.dev/github.com/NicoNex/echotron/v3#NewInlineMessageID).
- Optional parameters can be added by passing the correct struct to each method that might request optional parameters. If you don't want to pass any optional parameter, `nil` is more than enough. Refer to the [docs](https://pkg.go.dev/github.com/NicoNex/echotron/v3) to check for each method's optional parameters struct: it's the type of the `opts` parameter.
- Some parameters are hardcoded to avoid putting random stuff which isn't recognized by the Telegram API. Some notable examples are [`ParseMode`](https://github.com/NicoNex/echotron/blob/master/options.go#L21), [`ChatAction`](https://github.com/NicoNex/echotron/blob/master/options.go#L54) and [`InlineQueryType`](https://github.com/NicoNex/echotron/blob/master/inline.go#L27). For a full list of custom hardcoded parameters, refer to the [docs](https://pkg.go.dev/github.com/NicoNex/echotron/v3) for each custom type: by clicking on the type's name, you'll get the source which contains the possible values for that type.

## Additional examples
### Functional approach to state management
```golang
package main

import (
"log"
"strings"

"github.com/NicoNex/echotron/v3"
)

// Recursive type definition of the bot state function.
type stateFn func(*echotron.Update) stateFn

type bot struct {
chatID int64
state stateFn
name string
echotron.API
}

const token = "MY TELEGRAM TOKEN"

func newBot(chatID int64) echotron.Bot {
bot := &bot{
chatID: chatID,
API: echotron.NewAPI(token),
}
// We set the default state to the bot.handleMessage method.
bot.state = bot.handleMessage
return bot
}

func (b *bot) Update(update *echotron.Update) {
// Here we execute the current state and set the next one.
b.state = b.state(update)
}

func (b *bot) handleMessage(update *echotron.Update) stateFn {
if strings.HasPrefix(update.Message.Text, "/set_name") {
b.SendMessage("Send me my new name!", b.chatID, nil)
// Here we return b.handleName since next time we receive a message it
// will be the new name.
return b.handleName
}
return b.handleMessage
}

func (b *bot) handleName(update *echotron.Update) stateFn {
b.name = update.Message.Text
b.SendMessage(fmt.Sprintf("My new name is %q", b.name), b.chatID, nil)
// Here we return b.handleMessage since the next time we receive a message
// it will be handled in the default way.
return b.handleMessage
}

func main() {
dsp := echotron.NewDispatcher(token, newBot)
log.Println(dsp.Poll())
}
```

### Self destruction for lower memory footprint
```golang
package main

import (
"log"
"time"

"github.com/NicoNex/echotron/v3"
)

type bot struct {
chatID int64
echotron.API
}

const token = "MY TELEGRAM TOKEN"

var dsp *echotron.Dispatcher

func newBot(chatID int64) echotron.Bot {
bot := &bot{
chatID,
echotron.NewAPI(token),
}
go bot.selfDestruct(time.After(time.Hour))
return bot
}

func (b *bot) selfDestruct(timech <-chan time.Time) {
<-timech
b.SendMessage("goodbye", b.chatID, nil)
dsp.DelSession(b.chatID)
}

func (b *bot) Update(update *echotron.Update) {
if update.Message.Text == "/start" {
b.SendMessage("Hello world", b.chatID, nil)
}
}

func main() {
dsp = echotron.NewDispatcher(token, newBot)
log.Println(dsp.Poll())
}
```

### Webhook

```golang
package main

import "github.com/NicoNex/echotron/v3"

type bot struct {
chatID int64
echotron.API
}

const token = "MY TELEGRAM TOKEN"

func newBot(chatID int64) echotron.Bot {
return &bot{
chatID,
echotron.NewAPI(token),
}
}

func (b *bot) Update(update *echotron.Update) {
if update.Message.Text == "/start" {
b.SendMessage("Hello world", b.chatID, nil)
}
}

func main() {
dsp := echotron.NewDispatcher(token, newBot)
dsp.ListenWebhook("https://example.com:443/my_bot_token")
}
```

### Webhook with a custom http.Server

This is an example for a custom http.Server which handles your own specified routes
and also the webhook route which is specified by ListenWebhook.

```golang
package main

import (
"github.com/NicoNex/echotron/v3"

"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
)

type bot struct {
chatID int64
echotron.API
}

const token = "MY TELEGRAM TOKEN"

func newBot(chatID int64) echotron.Bot {
return &bot{
chatID,
echotron.NewAPI(token),
}
}

func (b *bot) Update(update *echotron.Update) {
if update.Message.Text == "/start" {
b.SendMessage("Hello world", b.chatID, nil)
}
}

func main() {
termChan := make(chan os.Signal, 1) // Channel for terminating the app via os.Interrupt signal
signal.Notify(termChan, syscall.SIGINT, syscall.SIGTERM)

mux := http.NewServeMux()
mux.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
// Handle user login
})
mux.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) {
// Handle user logout
})
mux.HandleFunc("/about", func(w http.ResponseWriter, r *http.Request) {
// Tell something about your awesome telegram bot
})

// Set custom http.Server
server := &http.Server{Addr: ":8080", Handler: mux}

go func() {
<-termChan
// Perform some cleanup..
if err := server.Shutdown(context.Background()); err != nil {
log.Print(err)
}
}()

// Capture the interrupt signal for app termination handling
dsp := echotron.NewDispatcher(token, newBot)
dsp.SetHTTPServer(server)
// Start your custom http.Server with a registered /my_bot_token handler.
log.Println(dsp.ListenWebhook("https://example.com/my_bot_token"))
}
```