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 / для этого урока, потому что он очень помог мне понять, как мы можем создать бота и развернуть его.

  1. Установите ngrok отсюда https://ngrok.com/download
  2. После установки вы запускаете на своем терминале следующую команду:
ngrok http 3000

3. Теперь вы должны иметь возможность видеть свой общедоступный IP:

golang телеграм бот

В данном случае это: 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 структура.

Я объясню, как работает этот фрагмент кода:

  1. wordsPlay(chatId int64, string_input string) – это основная функция данного варианта использования. Создаёт URL-адрес для запроса.
  2. getWordData(url string) получает URL и запрашивает API, после получения результата создаёт структуру из ранее определённой структуры ответа.
  3. Основная функция (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”. После скриншотов вы сможете увидеть полный код.

golang телеграм бот
golang телеграм бот

Полный код

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))
}

Ваши последующие шаги

Вы могли бы создать бота, который будет выдавать синонимы для необходимых слов. Это очень помогло бы вам при написании писем, сообщений или любых других видов текстов.

+1
1
+1
1
+1
0
+1
0
+1
0

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *