golang телеграм бот
В этой статье вы узнаете, как легко установить связь между вашим ботом и программой, написанной на языке программирования Golang.
@Golang – подпишись на Go в телеграме, гайды, обзор библиотек и много практики.
Цели
Ваш бот будет обрабатывать два возможных варианта:
- Словарь: вы отправляете слово, а бот присылает вам различные значения, основанные на классификации слова по частям речи
- Информация об авторе: отвечает на запрос информации об авторе
Создание Telegram-бота
Первое, что вам следует сделать, это создать бота в своём аккаунте Telegram. Для этого вам нужно будет найти в Телеграмме BotFather
и отправить ему команду /newbot
, предварительно выбрав имя для вашего бота. После этого вы получите токен. Этот токен очень важен, он как пароль для управления вашим ботом.
Первые шаги
В Telegram существуют серверы, которые работают как промежуточное программное обеспечение между чатами и приложением. Они очень похожи на почтовое отделение, но вместо почтового индекса здесь у вас есть chat_id.
Вы должны создать структуру для своего бота, чтобы он мог конкретно отвечать на запросы пользователей. Итак, мы создадим структуру для обработки запросов от Telegram.
Здесь вы можете посмотреть другие поля, включённые в запрос: https://core.telegram.org/bots/api#update
import requests, uvicorn, json
from fastapi import FastAPI, Form, Response
from twilio.twiml.messaging_response import MessagingResponse
bot_app = FastAPI()
def formatOutput(Body, data):
if 'error' not in data:
data_output = json.dumps(data['nutritions'])
data_output = data_output.replace(" ", "") #remove whitespaces
else:
data_output = json.dumps(data)
data_output = data_output.replace(":", "\n") #create paragraphs
data_output = data_output.replace('"', '') #get only the nutrition information
data_output = data_output.replace(",", "\n") #create paragraphs
data_output = data_output.replace("{","").replace("}", "") #remove brackets
data_output = Body +"\n \n" + data_output #add the header
return data_output
def getInfoFruit(fruit_name):
fruit_name = fruit_name.lower()
url = 'https://fruityvice.com/api/fruit/{}'.format(fruit_name)
resp = requests.get(url)
if resp.status_code != 200:
return {"error": "not a fruit"}
data = resp.json()
return data
@bot_app.post("/bot")
async def chat(Body: str = Form(...)):
data = getInfoFruit(Body)
output = formatOutput(Body, data)
response = MessagingResponse()
msg = response.message(output)
return Response(content=str(response), media_type="application/xml")
if __name__ == "__main__":
uvicorn.run(bot_app, host="0.0.0.0", port=5000)
Вы должны изменить “YOUR_TOKEN” на токен, который предоставит вам BotFather
, поэтому “bot” + YOUR_TOKEN
.
Но… что он будет делать?
Легко понять, что каждый раз, когда запускается обработчик, это означает, что если вы отправляете сообщение внутри чата со своим ботом (да, вы должны открыть чат с созданным вами ботом) — он ответит вам: Hello World.
Внедрение вашего бота “Hello World”
Я должен выразить огромную благодарность автору https://www.sohamkamani.com/golang/telegram-bot / для этого урока, потому что он очень помог мне понять, как мы можем создать бота и развернуть его.
- Установите
ngrok
отсюда https://ngrok.com/download - После установки вы запускаете на своем терминале следующую команду:
ngrok http 3000
3. Теперь вы должны иметь возможность видеть свой общедоступный IP:
В данном случае это: https://d860-85-245-152-103.eu.ngrok.io
4. Откройте терминал заново и запустите:
curl -F "url=https://d860-85-245-152-103.eu.ngrok.io" https://api.telegram.org/bot+YOUR_TOKEN/setWebhook
(следует добавить после “bot” ваш личный токен)
После запуска этой команды вы должны увидеть:
{"ok":true,"result":true,"description":"Webhook was set"}
Достаточно быстро и легко создать общедоступный IP-адрес и подключиться к вашему локальному IP 3000. Это не производственное решение, потому что ngrok
позволяет вам иметь один и тот же общедоступный IP-адрес только в течение 2 часов, используя бесплатный доступ. Но в любом случае, это действительно полезно знать для вашего развития.
Протестируйте вашего бота
Зайдите в чат своего бота и напишите что-нибудь, и бот ответит вам ”Hello Word”.
Пример использования словаря
Мы будем использовать FREE DICTIONARY API, https://dictionaryapi.dev /. Это действительно полный API, который предоставляет нам информацию о: значениях, синонимах, антонимах, частях речи и т.д. Чтобы использовать это, мы должны создать запрос к API, подобный приведённому ниже примеру:
В качестве примера, чтобы получить определение английского слова hello, вы можете отправить запрос на
https://api.dictionaryapi.dev/api/v2/entries/en/hello
Для этого урока я решил использовать только значения слова, но идея будет заключаться в том, чтобы получить одно определение для каждой части речи. Например, если мы выберем слово TIME, API найдет 3 части речи, и первое определение для каждой будет:
- noun: Неизбежное продвижение в будущее с прохождением настоящих и прошлых событий.
- verb: Измерять или записывать время, продолжительность или скорость.
- interjection: напоминание судьи игрокам продолжить игру после паузы.
Итак, в этом случае сообщение должно отображаться следующим образом:
noun : The inevitable progression into the future with the passing of present and past events.
verb : To measure or record the time, duration, or rate of.
interjection : Reminder by the umpire for the players to continue playing after their pause.
Код для этого:
// Store data from the Api
type Response []struct {
Word string `json:"word"`
Phonetic string `json:"phonetic,omitempty"`
Phonetics []struct {
Text string `json:"text"`
Audio string `json:"audio"`
SourceURL string `json:"sourceUrl,omitempty"`
License struct {
Name string `json:"name"`
URL string `json:"url"`
} `json:"license,omitempty"`
} `json:"phonetics"`
Meanings []struct {
PartOfSpeech string `json:"partOfSpeech"`
Definitions []struct {
Definition string `json:"definition"`
Synonyms []string `json:"synonyms"`
Antonyms []interface{} `json:"antonyms"`
Example string `json:"example,omitempty"`
} `json:"definitions"`
Synonyms []string `json:"synonyms"`
Antonyms []interface{} `json:"antonyms"`
} `json:"meanings"`
License struct {
Name string `json:"name"`
URL string `json:"url"`
} `json:"license"`
SourceUrls []string `json:"sourceUrls"`
}
func wordsPlay(chatID int64, string_input string) error {
requestURL := "https://api.dictionaryapi.dev/api/v2/entries/en/" + string_input
response, err := getWordData(requestURL)
if err != nil {
sendMessage(chatID, "Meaning not found for the word: "+string_input)
return err
}
// Return the 1st definition that comes for each part of speech
err = sendMessage(chatID, formatResponse(response))
return err
}
func getWordData(url string) (Response, error) {
var result Response
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
fmt.Printf("client: could not create request: %s\n", err)
os.Exit(1)
}
res, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Printf("client: error making http request: %s\n", err)
os.Exit(1)
}
fmt.Printf("client: got response!\n")
fmt.Printf("client: status code: %d\n", res.StatusCode)
body, err := ioutil.ReadAll(res.Body) // response body is []byte
if err != nil {
return result, err
}
// read json data into a Result struct
err = json.Unmarshal(body, &result)
if err != nil {
return result, err
}
return result, nil
}
func formatResponse(response Response) string {
var str_response string
var elem_str string
for _, element := range response[0].Meanings {
elem_str = element.PartOfSpeech + " : " + element.Definitions[0].Definition + "\n"
str_response += elem_str
}
return str_response
}
В этом фрагменте кода у нас есть 3 функции и 1 структура.
Я объясню, как работает этот фрагмент кода:
wordsPlay(chatId int64, string_input string)
– это основная функция данного варианта использования. Создаёт URL-адрес для запроса.getWordData(url string)
получает URL и запрашивает API, после получения результата создаёт структуру из ранее определённой структуры ответа.- Основная функция (
wordsPlay
) проверяет, является ли результат из Dictionary API действительным, если нет, отправляет сообщение с надписью:
“Meaning not found for the word: “+string_input
Если результат действителен, то вызывает функцию для форматирования выходных данных.
4. formatResponse(response Response
) получает объект Response, а затем создаёт строку, используя значения для каждой из найденных частей речи:
for _, element := range response[0].Meanings {
elem_str = element.PartOfSpeech + " : " + element.Definitions[0].Definition + "\n"
str_response += elem_str
}
5. Отправляет ответ боту, используя chat_id
Чтобы этот код заработал, нам нужно обновить код обработчика, но давайте реализуем авторский вариант использования. В этом простом случае мы хотим ответить, указав только имя автора бота и местоположение:
func author(chatID int64) error {
err := sendMessage(chatID, "Alberto Vilas ; Lisbon - Portugal")
if err != nil {
log.Fatal(err)
}
return nil
}
Чтобы всё сработало, нам нужно создать записи для функций. Это означает, что нам нужно определить, когда мы должны вызывать “wordsPlay”, а когда “author”. После скриншотов вы сможете увидеть полный код.
Полный код
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"github.com/sirupsen/logrus"
)
// Create a new instance of the logger. You can have any number of instances.
var log = logrus.New()
// Create a struct that mimics the webhook response body
// https://core.telegram.org/bots/api#update
type webhookReqBody struct {
Message struct {
Text string `json:"text"`
Chat struct {
ID int64 `json:"id"`
} `json:"chat"`
} `json:"message"`
}
// Store data from the Api
type Response []struct {
Word string `json:"word"`
Phonetic string `json:"phonetic,omitempty"`
Phonetics []struct {
Text string `json:"text"`
Audio string `json:"audio"`
SourceURL string `json:"sourceUrl,omitempty"`
License struct {
Name string `json:"name"`
URL string `json:"url"`
} `json:"license,omitempty"`
} `json:"phonetics"`
Meanings []struct {
PartOfSpeech string `json:"partOfSpeech"`
Definitions []struct {
Definition string `json:"definition"`
Synonyms []string `json:"synonyms"`
Antonyms []interface{} `json:"antonyms"`
Example string `json:"example,omitempty"`
} `json:"definitions"`
Synonyms []string `json:"synonyms"`
Antonyms []interface{} `json:"antonyms"`
} `json:"meanings"`
License struct {
Name string `json:"name"`
URL string `json:"url"`
} `json:"license"`
SourceUrls []string `json:"sourceUrls"`
}
func sendMessage(chatID int64, text string) error {
// Create the request body struct
reqBody := &sendMessageReqBody{
ChatID: chatID,
Text: text,
}
// Create the JSON body from the struct
reqBytes, err := json.Marshal(reqBody)
if err != nil {
return err
}
// Send a post request with your token
res, err := http.Post("https://api.telegram.org/bot+YOUR_TOKEN/sendMessage", "application/json", bytes.NewBuffer(reqBytes))
if err != nil {
return err
}
if res.StatusCode != http.StatusOK {
return errors.New("unexpected status" + res.Status)
}
return nil
}
// This handler is called everytime telegram sends us a webhook event
func Handler(res http.ResponseWriter, req *http.Request) {
// First, decode the JSON response body
body := &webhookReqBody{}
if err := json.NewDecoder(req.Body).Decode(body); err != nil {
fmt.Println("could not decode request body", err)
return
}
chat_id := body.Message.Chat.ID
text := body.Message.Text
log.Info("body handle: \n")
log.Info(body)
log.Info("TEXT: " + text)
if strings.Contains(strings.ToLower(text), "author") {
err := author(chat_id)
if err != nil {
fmt.Println("error in sending reply:", err)
}
} else if text != "" {
wordsPlay(chat_id, text)
}
// log a confirmation message if the message is sent successfully
fmt.Println("reply sent")
}
//The below code deals with the process of sending a response message
// to the user
type sendMessageReqBody struct {
ChatID int64 `json:"chat_id"`
Text string `json:"text"`
}
func author(chatID int64) error {
err := sendMessage(chatID, "Alberto Vilas ; Lisbon - Portugal")
if err != nil {
log.Fatal(err)
}
return nil
}
func getWordData(url string) (Response, error) {
var result Response
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
fmt.Printf("client: could not create request: %s\n", err)
os.Exit(1)
}
res, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Printf("client: error making http request: %s\n", err)
os.Exit(1)
}
fmt.Printf("client: got response!\n")
fmt.Printf("client: status code: %d\n", res.StatusCode)
body, err := ioutil.ReadAll(res.Body) // response body is []byte
if err != nil {
return result, err
}
// read json data into a Result struct
err = json.Unmarshal(body, &result)
if err != nil {
return result, err
}
return result, nil
}
func formatResponse(response Response) string {
var str_response string
var elem_str string
for _, element := range response[0].Meanings {
elem_str = element.PartOfSpeech + " : " + element.Definitions[0].Definition + "\n"
str_response += elem_str
}
log.Info("RESPONSE: ")
log.Info(str_response)
return str_response
}
func wordsPlay(chatID int64, string_input string) error {
requestURL := "https://api.dictionaryapi.dev/api/v2/entries/en/" + string_input
response, err := getWordData(requestURL)
if err != nil {
sendMessage(chatID, "Meaning not found for the word: "+string_input)
return err
}
// Return the 1st definition that comes for each part of speech
err = sendMessage(chatID, formatResponse(response))
return err
}
func log_init() error {
file, err := os.OpenFile("log_file.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err == nil {
log.Out = file
} else {
log.Info("Failed to log to file, using default stderr")
}
return nil
}
func main() {
//init the log file
err := log_init()
if err != nil {
os.Exit(1)
}
http.ListenAndServe(":3000", http.HandlerFunc(Handler))
}
Ваши последующие шаги
Вы могли бы создать бота, который будет выдавать синонимы для необходимых слов. Это очень помогло бы вам при написании писем, сообщений или любых других видов текстов.