Как я Telegram бота на Go писал. Часть первая. Начало.
В этой серии заметок хотел бы немного коснуться данной темы. Серия будет включаться в себя три раздела:
- Разработка бота на Go для поиска заклинаний для Dungeon And Dragons
- Публикация бота на платформе heroku
- Добавление аналитики использования бота
Итак, начнём!
Во-первых, нам потребуется сам список заклинаний DnD. Найти его можете в моём
bitbucket-репозитории этого
бота.
Во-вторых, создать новую папку. Например, dndspellsbot. В-третьих, получить
ключ для вашего нового бота у служебного аккаунта
@BotFatherBot, отправив ему команду /newbot
и сохранить полученный в ответ токен в безопасное место. В-четвертых, чтобы не
изобретать велосипед, добавить библиотеку telegram-bot-
api:
go get gopkg.in/telegram-bot-api.v4
И, наконец-то, в-пятых, написать код самого бота.
config.json
Создадим пустой Go-файл dndspellsbot.go в папке dndspellsbot. Туда же
добавим конфигурационный файл config.json со следующим содержимым:
{
 "TelegramBotToken": "токен-полученный-от-BotFather"
}
Конечно же, можно обойтись без этих усложнений и зашить токен прямо в коде бота, но в случае, если вы случайно зальетё код вашего бота в какой-то публичный репозиторий на github/bitbucket/whatever, то утечёт и токен, а это, согласитесь, неприятно. И это только сейчас у нас такой простой конфиг - в дальнейшем будут добавляться новые свойства и всё будет храниться в одном месте. Удобно, согласитесь? Получить этот токен из конфигурационного файла довольно просто стандартными средствами Go:
package main
import (
    "fmt"
    "gopkg.in/telegram-bot-api.v4"
    "log"
    "os"
    "encoding/json"
)
type Config struct {
    TelegramBotToken string
}
func main() {
    file, _ := os.Open("config.json")
    decoder := json.NewDecoder(file)
    configuration := Config{}
    err := decoder.Decode(&configuration)
    if err != nil {
       log.Panic(err)
    }
    fmt.Println(configuration.TelegramBotToken)
}
Теперь при добавлении в config.json новых свойств и модификации структуры
Config можно получать необходимые параметры конфигурации.
ping?
В репозитории telegram-bot-api есть пример echo-бота - бота, который
отвечает вам те же сообщением. Возьмём его за основу:
bot, err := tgbotapi.NewBotAPI(configuration.TelegramBotToken)
if err != nil {
    log.Panic(err)
}
bot.Debug = false
log.Printf("Authorized on account %s", bot.Self.UserName)
u := tgbotapi.NewUpdate(0)
u.Timeout = 60
updates, err := bot.GetUpdatesChan(u)
if err != nil {
    log.Panic(err)
}
// В канал updates будут приходить все новые сообщения.
for update := range updates { 
    // Создав структуру - можно её отправить обратно боту
    msg := tgbotapi.NewMessage(update.Message.Chat.ID, update.Message.Text)
    msg.ReplyToMessageID = update.Message.MessageID
    bot.Send(msg)
}
Вы уже добавила вашего бота в свой список контактов? Если нет - сейчас самое
время. Теперь самое интересное - запускаем наше приложение командой go run dndspellsbot.go и пишем что-нибудь нашему боту. Если всё сделано правильно,
то бот ответит вам вашим же сообщением:
 Однако, мы хотели не этого. Опустим подробности парсинга XML - пост не об этом
(полный код можно будет найти по ссылке в конце статьи). Будем считать, что у
нас есть слайс всех заклинаний и любой запрос пользователя к боту - это запрос
на поиск подходящего заклинания по его имени (усложнять можно бесконечно, но
на данный момент этого достаточно):
Однако, мы хотели не этого. Опустим подробности парсинга XML - пост не об этом
(полный код можно будет найти по ссылке в конце статьи). Будем считать, что у
нас есть слайс всех заклинаний и любой запрос пользователя к боту - это запрос
на поиск подходящего заклинания по его имени (усложнять можно бесконечно, но
на данный момент этого достаточно):
...
query := update.Message.Text
// Получим те заклинания, в имени которых есть искомое слово или фраза
filteredSpells := Filter(spells.Spells, func(spell Spell) bool { 
    return strings.Index(strings.ToLower(spell.Name), strings.ToLower(query)) >= 0
})
// Если не нашлось ни одного заклинания - скажем об этом пользователю
if len(filteredSpells) == 0 {
    msg := tgbotapi.NewMessage(update.Message.Chat.ID, "No one spells matches")
    bot.Send(msg) 
}
// Каждое заклинание отправляем отдельным сообщением
for _, spell := range(filteredSpells) {
    text := ""
    for _, t := range(spell.Texts) {
        text = text + t + "\n"
    }
    
    msg := tgbotapi.NewMessage(update.Message.Chat.ID, fmt.Sprintf("%s\n%s", spell.Name, text))
    bot.Send(msg)
}
...
// Функция фильтрации слайсов
func Filter(spells []Spell, fn func(spell Spell) bool) []Spell {
    var filtered []Spell
    for _, spell := range(spells) {
        if fn(spell) {
            filtered = append(filtered, spell)
        }
    }
    return filtered
}
Теперь, при любом запросе от пользователя, бот будет возвращать все подходящие
заклинания из phb.xml. В следующей статье немного улучшим нашего бота:
добавим форматирование сообщений, inline-режим и добавим пару команд. Код бота
можно найти на bitbucket.
