✈️ Telegram бот на golang (основная часть)
После реализации бота на Ruby, мне захотелось сделать ещё одного, но уже на моём любимом языке - Golang.
Когда я только начинал работу, информации о библиотеке go-telegram-bot-api у меня не было. Так что пришлось писать бота практически с нуля. В проекте на Ruby все самое сложное делал гем telegram-bot-ruby. Но в этот раз я захотел сам реализовать бота.
После потраченного вечера на поиск информации об архитектуре телеграм ботов, я занялся написанием самого проекта.
Main():
err := initConfig()
if err != nil {
fmt.Println("Config error: ", err)
return
}Сначала бот инициализирует конфиг (ключи и токены) вызывая функцию initConfig(), которая выглядит следующим образом:
func initConfig() error {
viper.AddConfigPath("configs")
viper.SetConfigName("config")
return viper.ReadInConfig()
}Тут я использую библиотеку viper для считывания ключей и токенов из файла config.json
После инициализации конфига бот выполняет проверку переменной err, в которую в случае ошибки будет записан код ошибки (точнее не код, а сама ошибка). Если переменная err равна nil, бот продолжит работу, в противном случае в консоль выведется ошибка и main() закроется.
в языке Golang значение nil означает буквально "ничего"
botUrl := "https://api.telegram.org/bot" + viper.GetString("token")
offSet := 0
for {
updates, err := getUpdates(botUrl, offSet)
if err != nil {
fmt.Println("Something went wrong: ", err)
return
}
for _, update := range updates {
respond(botUrl, update)
offSet = update.UpdateId + 1
}
fmt.Println(updates)
}После работы с инициализацией конфига, бот записывает в переменную botUrl ссылку, по которой он будет получать сообщения, или же слайс объектов Update. botUrl состоит из токена, полученного ранее из файла config.json и ссылки на api.telegram.org. Идентификатор offset нужен для обработки нескольких сообщений.
Далее идёт главный цикл бота, в котором происходит вообще всё. Для работы с пользователем боту необходимо получить от него сообщение, это реализовано с помощью функции getUpdates():
getUpdates()
func getUpdates(botUrl string, offset int) ([]mods.Update, error) {
resp, err := http.Get(botUrl + "/getUpdates?offset=" +
strconv.Itoa(offset))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var restResponse mods.TelegramResponse
err = json.Unmarshal(body, &restResponse)
if err != nil {
return nil, err
}
return restResponse.Result, nil
}Построчно расписывать не буду, весь процесс в этой функции можно расписать так:
Бот отправляет запрос GET телеграму, на что получает json файл, который переводит в слайс byte'ов. Слайс байтов записывается в структуру restResponse, которая имеет только одно поле - "Result". Result является слайсом объектов Update. В конце функция вернёт поле "Result", т.е слайс обновлений.
type TelegramResponse struct {
Result []Update `json:"result"`
}type Update struct {
UpdateId int `json:"update_id"`
Message Message `json:"message"`
}Структура Update имеет всего два поля: идентификатор обновления и объект Message:
type Message struct {
Chat Chat `json:"chat"`
Text string `json:"text"`
Sticker Sticker `json:"sticker"`
}У структуры Message есть поле Chat, которое бот использует только для работы с идентификатором чата. Поле Text отвечает за текст сообщения, а поле Sticker нужно для обработки сообщений, состоящих из стикера.
После получения слайса обновлений, бот проходится по каждому объекту Update функцией respond().
respond()
func respond(botUrl string, update mods.Update) error {
msg := strings.ToLower(update.Message.Text)
if msg != "" {
switch msg {
...
}
}В этой функции я описал весь функционал бота, сначала записывает в переменную msg сообщение, которое берет из объекта Update и переводит в нижний регистр, после чего идёт проверка на то, что отправил пользователь.
Если на вход пришло сообщение, функция пройдется по нему с помощью оператора switch:
switch msg {
case "/check":
mods.Check(botUrl, update)
return nil
case "/git":
mods.CheckGit(botUrl, update)
return nil
case "/weather7":
mods.SendDailyWeather(botUrl, update, 7)
return nil
...
}При каждой команде, вызывается определённая функция, после чего respond() возвращает nil, что означает, что всё прошло без ошибок.
} else {
if update.Message.Sticker.File_id != "" {
mods.SendRandomSticker(botUrl, update)
return nil
}
mods.SendMsg(botUrl, update, "Пока я воспринимаю только текст и стикеры 🤷🏻♂️")
return nil
}Если же пользователь отправил не текст, а что-то иное, бот сделает проверку на стикер. Если пользователь отправил стикер, бот тоже отправляет в ответ случайный стикер. В остальных случаях бот уведомит пользователя, что воспринимает только текст и стикеры.
Осталось рассказать о командах, но это я сделаю в следующей статье. В целом, это оказался очень интересный проект, над которым я работаю до сих пор. Мало того, что во время реализации бота я накапливаю опыт работы с API, так ещё и получаю очень полезного помощника, который подскажет погоду, курс криптовалюты, отправит смешную картинку с попугаем или котом, найдёт страну по IP адресу, и даже я не знаю, как его функционал изменится через неделю.....