В этой серии заметок хотел бы немного коснуться данной темы. Серия будет включаться в себя три раздела:

  1. Разработка бота на Go для поиска заклинаний для Dungeon And Dragons
  2. Публикация бота на платформе heroku
  3. Добавление аналитики использования бота

Итак, начнём!

Во-первых, нам потребуется сам список заклинаний 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 и пишем что-нибудь нашему боту. Если всё сделано правильно, то бот ответит вам вашим же сообщением: 2016-10-03_21-46-08.png Однако, мы хотели не этого. Опустим подробности парсинга 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.