用 Go 寫 Telegram Bot

用 Go 寫 Telegram Bot

Issue

Telegram Bot 沒有任何使用限制,可以寫出許多屬於自己的幫手。本篇簡單的用 Go 寫一個鸚鵡機器人,用戶說什麼,機器人就回什麼。

新增自己的 Bot

image

Telegram 非常有趣,要新增自己的 Bot,只要跟 Telegram 上的 BotFather 說一下,他就會給你一個 Bot。

image

按 BotFather 給你的步驟做,最後可以看到他會提供一個 API Token 給你,只要有這個 Token 就可以跟 Bot 溝通,要保存好,等等會用到,也不要給別人看到,以免自己的 Bot 變成別人的玩具。

go-telegram-bot-api

Go 已經有社區開發的函式庫可以用 telegram-bot-api

go get -u github.com/go-telegram-bot-api/telegram-bot-api/v5

之後只要 import 就可以使用:

import (
    tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)

連線到 Telegram Bot

利用剛剛 import 的函式庫,只需要剛剛創建好的 Bot 的 API Token 就可以連線到 Telegram。

bot, err := tgbotapi.NewBotAPI(os.Getenv("TELEGRAM_API_TOKEN"))
if err != nil {
    panic(err)
}

上面用環境變數的方式來取得 TOKEN,避免需要直接寫在程式碼裡,如果覺得在測試時設定環境變數很麻煩,可以用 JetBrains 的 GoLand,設定 Runtime 的環境變數:

image

處理 Update

要接收使用者傳過來的訊息,Telegram API 稱為 Update,我們使用的函示庫基本上都自動處理好了,只需要接收一個 Channel 就好。使用 bot.GetUpdatesChan(config) 會需要給一個 Config,這可以用 tgbotapi.NewUpdate(0) 來生,然後這邊設定 Timeout 為 60,如果對這個設定值有興趣,可以去看 Telegram 的文件。

updateConfig := tgbotapi.NewUpdate(0)
updateConfig.Timeout = 60
updates := bot.GetUpdatesChan(updateConfig)

回復

我們先簡單寫一隻鸚鵡,把用戶傳過來的 Text,直接傳回去。

image

用 for 把 Channel 中的 Update 一個一個拿出來,然後用 tgbotapi 產生一個 Message 物件,裡面放在聊天室的 ID 和要回傳的 Text,最後用 bot 把 Message 傳回去。

for update := range updates {
    text := update.Message.Text
    chatID := update.Message.Chat.ID
    replyMsg := tgbotapi.NewMessage(chatID, text)
    _, _ = bot.Send(replyMsg)
}

簡單鸚鵡的完整程式碼

package main

import (
    tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
    "os"
)

func main() {
    bot, err := tgbotapi.NewBotAPI(os.Getenv("TELEGRAM_API_TOKEN"))
    if err != nil {
        panic(err)
    }
    bot.Debug = true
    updateConfig := tgbotapi.NewUpdate(0)
    updateConfig.Timeout = 60
    updates := bot.GetUpdatesChan(updateConfig)
    for update := range updates {
        text := update.Message.Text
        chatID := update.Message.Chat.ID
        replyMsg := tgbotapi.NewMessage(chatID, text)
        _, _ = bot.Send(replyMsg)
    }
}

改寫成非阻塞的 Goroutine

因為每次處理和回復都會需要一點時間,甚至是要去 DB 找資料,如果用上面的寫法,在處理多人或高速傳訊息的時候會被卡住,一次只能處理的一個訊息。還好 Go 的併發寫法非常簡單,只要放一個 go 在前面,就可以非阻塞的執行 function 中的內容。

我們先把 handleUpdate 提取成一個 function,然後再用 go 去執行他:

func main() {
    bot, err := tgbotapi.NewBotAPI(os.Getenv("TELEGRAM_API_TOKEN"))
    if err != nil {
        panic(err)
    }
    bot.Debug = true
    updateConfig := tgbotapi.NewUpdate(0)
    updateConfig.Timeout = 60
    updates := bot.GetUpdatesChan(updateConfig)
    for update := range updates {
        go handleUpdate(bot, update)
    }
}

func handleUpdate(bot *tgbotapi.BotAPI, update tgbotapi.Update) {
    text := update.Message.Text
    chatID := update.Message.Chat.ID
    replyMsg := tgbotapi.NewMessage(chatID, text)
    _, _ = bot.Send(replyMsg)
}

回復特定訊息

要回復 回復訊息 ,像是下面的效果也很簡單:

image

只需要設定 replyMsg.ReplyToMessageID 就可以回復特定的訊息,下面就是直接回覆接收到的訊息:

func handleUpdate(bot *tgbotapi.BotAPI, update tgbotapi.Update) {
    text := update.Message.Text
    chatID := update.Message.Chat.ID
    replyMsg := tgbotapi.NewMessage(chatID, text)
    replyMsg.ReplyToMessageID = update.Message.MessageID
    _, _ = bot.Send(replyMsg)
}

處理 Command

Telegram 中,用 / 開頭的文字就是 Command,這個函示庫也有寫好處理 Command 的方法,只需要用 isCommand(),確認一下是不是 Command 就可以:

func handleUpdate(bot *tgbotapi.BotAPI, update tgbotapi.Update) {
    text := update.Message.Text
    chatID := update.Message.Chat.ID
    replyMsg := tgbotapi.NewMessage(chatID, text)
    if update.Message.IsCommand() {
        switch update.Message.Command() {
        case "start":
            replyMsg.Text = "Hello " + update.Message.From.FirstName
        case "help":
            replyMsg.Text = "What can I help you?"
        default:
            replyMsg.Text = "No such command!!!"
        }
    }
    _, _ = bot.Send(replyMsg)
}

上面的程式碼其實也可以看到如何拿到傳送者的一些基本資料。

image

Reference


TonyPepe
Made with Love in Taiwan
Copyright
2020 - 2023