Telegram bots are powerful tools for automating tasks and interacting with users. They are also easy to build and deploy, so it looked like a good idea to build one to play around with Go. This post will guide you through building a customizable Telegram bot. If you’d like to see the final code or simply go and grab the ready-to-use script to customize and run, you can find on GitHub. The setup and running instructions are included through the post.

Understanding Telegram bots

A bot in Telegram is a special account that is not linked to a phone number. It can be used to receive messages and send responses. The bot API is simple and well-documented. Note that there are two ways to receive updates from a Telegram chat: via the getUpdates method, or by setting a webhook. The getUpdates method is a simple way to poll for new messages, but I’d recommend to use a webhook because they are faster and more reliable. This post will focus on setting up a webhook.

Creating a bot

To create a bot, you need to talk to the BotFather via Telegram. This bot will help you create and manage your bots. You can create a new bot by sending the /newbot command to the @BotFather. Follow the instructions to set up your bot and note down your API token.

Setting up the bot

Create a new project folder and initialize a Go module:

$ mkdir telegram-bot
$ cd telegram-bot
$ go mod init telegram-bot

Install the required dependencies. In this case, we will use godotenv to manage environment variables through a .env file, yaml.v2 to parse the triggers, and bluemonday to sanitize the input.

$ go get github.com/lpernett/godotenv github.com/microcosm-cc/bluemonday gopkg.in/yaml.v2

Loading the configuration from .env

Create a .env file in the project root with the following content:

TELEGRAM_BOT_TOKEN=your-telegram-bot-token
PORT=8080

Then, load the environment variables in the code:

err := godotenv.Load()
if err != nil {
    log.Fatal("Error loading .env file")
}
if os.Getenv("TOKEN") == "" || os.Getenv("PORT") == "" {
    log.Fatalf("TOKEN or PORT is missing")
}

Loading the YAML file

Create a triggers.yaml file in the project root with the following content:

- key: hello
  values: Hello!
- key: say hello to
  values: Hello __input__, how are you?

Then, load the triggers in the code:

type Trigger struct {
    Key    string      `yaml:"key"`
    Values interface{} `yaml:"values"`
}

var triggers []Trigger

func initializeTriggers() error {
    file, err := ioutil.ReadFile("./triggers.yml")
    if err != nil {
        return err
    }
    return yaml.Unmarshal(file, &triggers)
}

Handling incoming messages

Telegram sends updates using JSON. We need to parse the incoming message and send a response. The message structure is defined in the Telegram API.

type Update struct {
    Message Message `json:"message"`
}

type Message struct {
    Chat Chat   `json:"chat"`
    Text string `json:"text"`
}

type Chat struct {
    Id int `json:"id"`
}

Then, we can handle the incoming messages:

func callHandler(w http.ResponseWriter, r *http.Request) {
    var update Update
    err := json.NewDecoder(r.Body).Decode(&update)
    if err != nil {
        w.WriteHeader(http.StatusBadRequest)
        return
    }

    responseText := computeResponse(update.Message.Text)
    if responseText != "" {
        sendTextToChat(update.Message.Chat.Id, responseText)
    }
}

Computing the response

Match the incoming message with the triggers loaded from the YAML file:

func computeResponse(input string) string {
    for _, trigger := range triggers {
        if strings.Contains(strings.ToLower(input), strings.ToLower(trigger.Key)) {
            switch values := trigger.Values.(type) {
            case string:
                return values
            case []interface{}:
                return values[rand.Intn(len(values))].(string)
            }
        }
    }
    return ""
}

Setting up the web server

Listen for incoming requests using Go’s net/http package:

func main() {
    loadEnvVariables()
    err := initializeTriggers()
    if err != nil {
        log.Fatalf("Error initializing triggers: %s", err)
    }

    http.HandleFunc("/", callHandler)

    log.Printf("Server running on port %s", os.Getenv("PORT"))
    err = http.ListenAndServe(fmt.Sprintf(":%s", os.Getenv("PORT")), nil)
    if err != nil {
        log.Fatalf("Server error: %s", err)
    }
}

Sending messages

Call the Telegram bot API to send the message back to the user/chat:

func sendTextToChat(chatId int, text string) {
    url := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", os.Getenv("TOKEN"))
    _, err := http.PostForm(url, url.Values{
        "chat_id": {strconv.Itoa(chatId)},
        "text":    {text},
    })
    if err != nil {
        log.Printf("Error sending message: %s", err)
    }
}

Running

To run the bot, you need to set up the webhook. You can do so using the Telegram API:

curl -X POST "https://api.telegram.org/bot<YOUR_TOKEN>/setWebhook?url=<YOUR_SERVER_URL>"

Then, run the Go script:

$ go run main.go

Conclusion

Building a Telegram bot is a fun way to approach a new language or technology, since it’s pretty straightforward and you can see results quickly. This post covered the basics, but you can use it to automate tasks, send notifications, or even build a chatbot. The possibilities are endless, so go ahead and build your own!