✈️ 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 адресу, и даже я не знаю, как его функционал изменится через неделю.....