Привет, Хабр!
Возможно, многие сталкивались с задачей: есть сервер с некими cronjob-ами, результат выполнения которых хотелось бы мониторить определенному числу лиц, но при этом сервер находится в каком-нибудь intranet и не имеет доступа к сети интернет. Вот и у нас однажды возникла подобная проблема. Единственным доступным средством коммуникации с внешним миром у сервера был почтовый шлюз, через который можно было отправить электронную почту. До некоторого времени задача решалась отправкой нескольких копий письма, но со временем стало понятно, что гораздо удобнее читать уведомления в Telegram. Такую настройку мы произвели с помощью Yandex Cloud Functions.
Инструкция
Для решения задачи мы воспользовались непосредственно самими функциями с триггером для запуска — почтой.
По документации Яндекса для начала создаем сервисный аккаунт с правами
functions.functionInvoker
iam.serviceAccounts.user
Он будет использоваться триггером для запуска функции.
![](https://webcf.waybackmachine.org/web/20240229095317/https://habrastorage.org/getpro/habr/upload_files/512/4ce/cdc/5124cecdcfecb67fda464a224b2fc509.png)
Далее пишем нашу функцию, Яндекс предоставляет для этого множество сред разработки, но мы выбрали go, т.к. на данный момент пытаемся его активно внедрять в свою работу.
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"html"
"io/ioutil"
"log"
"net/http"
)
const telegramAPIBaseURL = "https://api.telegram.org/bot"
const chatId = 123456789
const botToken = "456346346346:Skdklsdf939fcjkDJDs3"
type Message struct {
ReceivedAt string `json:"received_at"`
Headers []Header `json:"headers"`
Attachments Attachment `json:"attachments"`
Message string `json:"message"`
}
type Header struct {
Name string `json:"name"`
Values []string `json:"values"`
}
type Attachment struct {
BucketId string `json:"bucket_id"`
Keys []string `json:"keys"`
}
type Email struct {
Messages []Message `json:"messages"`
}
type Request struct {
Message string `json:"message"`
Number int `json:"number"`
}
type ResponseBody struct {
Context context.Context `json:"context"`
Request interface{} `json:"request"`
Error string `json:"error"`
}
func Handler(ctx context.Context, email *Email) ([]byte, error) {
var eMessages []string
var errorMess string
for _, message := range email.Messages {
eMessages = append(eMessages, message.Message)
err := sendMessageToTelegram(message.Message, chatId)
if err != nil {
log.Println(err)
errorMess = err.Error()
}
}
body, err := json.Marshal(&ResponseBody{
Context: ctx,
Request: eMessages,
Error: errorMess,
})
if err != nil {
return nil, err
}
// Тело ответа необходимо вернуть в виде массива байтов
return body, nil
}
// sendMessageToTelegram отправляет сообщение в Telegram чат.
func sendMessageToTelegram(messageText string, chtid int) error {
// Telegram API endpoint для отправки сообщений
url := telegramAPIBaseURL + botToken + "/sendMessage"
// Подготовка данных для отправки
message := map[string]interface{}{
"chat_id": chtid,
"text": html.EscapeString(messageText),
}
messageBytes, err := json.Marshal(message)
if err != nil {
return fmt.Errorf("error marshaling message: %w", err)
}
// Создание HTTP запроса
req, err := http.NewRequest("POST", url, bytes.NewBuffer(messageBytes))
if err != nil {
return fmt.Errorf("error creating request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
// Отправка запроса
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("error sending request to Telegram: %w", err)
}
defer resp.Body.Close()
// Проверка на успешный ответ от Telegram
if resp.StatusCode != http.StatusOK {
responseData, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf("error from Telegram, status code: %d, response: %s", resp.StatusCode, string(responseData))
}
return nil
}
В коде есть две константы botToken
(токен бота в telegram) и chatId
(идентификатор чата). Получить botToken
можно создав нового бота отца всех ботов
Далее получаем chatId
Для этого нужно:
Добавить вашего бота в существующий чат или создать новый чат, в котором вы хотите получать уведомления.
Написать боту любое сообщение в этом чате.
Перейти по ссылке api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/getUpdates и найти в ответе заветный
chatId
Сам код, в общем-то, должен просто разбирать сообщение от триггера и отправлять нужный контент в telegram. Сообщение от триггера выглядит примерно так:
"messages":[
{
"received_at":"2022-09-15 14:42:23.983842092 +0000 UTC m=+260285.403254765",
"headers":[
{
"name":"X-Yandex-Fwd",
"values":[
"1"
]
},
...
{
"name":"Authentication-Results",
"values":[
"myt6-22bd3499f8ff.qloud-c.yandex.net; dkim=pass [email protected]"
]
}
],
"attachments":{
"bucket_id":"trigger-bucket-id",
"keys":[
"attachement-object-key1",
"attachement-object-key2"
]
},
"message":"<div>This is example body for documentation</div>\r\n"
}
]
Далее сохраняем нашу функцию, ждем окончания сборки и тестируем. Переходим из редактора на вкладку “тестирование”, и в поле “входные данные” вставляем пример сообщения:
![](https://webcf.waybackmachine.org/web/20240229095317/https://habrastorage.org/getpro/habr/upload_files/88f/5ec/96a/88f5ec96a10243cd631079205e8c18e2.png)
После выполнения, ответ отобразится в данном поле:
![](https://webcf.waybackmachine.org/web/20240229095317/https://habrastorage.org/getpro/habr/upload_files/532/544/995/532544995a487f0b2f5a627f268f83ba.png)
А в telegram должно прийти сообщение из поля message
Осталось только создать триггер запуска. В этом же разделе функций переходим в триггеры
![](https://webcf.waybackmachine.org/web/20240229095317/https://habrastorage.org/getpro/habr/upload_files/f28/d6b/9c2/f28d6b9c2b0ff3f4f6062883627291ab.png)
и создаем новый. Для этого указываем имя, тип (почта), вашу функцию, версию функции и сервисный аккаунт который создали ранее:
![](https://webcf.waybackmachine.org/web/20240229095317/https://habrastorage.org/getpro/habr/upload_files/e86/f24/93d/e86f2493db31e189eea4a3599f331a57.png)
После создания заходим в наш триггер и смотрим адрес электронной почты, на которую нужно отправить письмо для срабатывания триггера:
![](https://webcf.waybackmachine.org/web/20240229095317/https://habrastorage.org/getpro/habr/upload_files/7dd/25f/7cd/7dd25f7cde3800528b4197784ebf858d.png)
Теперь можно протестировать и сам триггер. Для этого отправляем произвольное письмо на сгенерированную почту и видим, что в telegram почти моментально прилетело исходное сообщение.
Иногда требуется отправлять одно и то же сообщение в несколько чатов, и если текст сообщения одинаковый, то оно может прийти только в первый чат. Чтобы исправить эту ситуацию, нужно добавить задержку между отправками, например так:
// Задержка в секундах
delay := time.Second * 1
...
err := sendMessageToTelegram(message.Message, chatId)
...
time.Sleep(delay)
Заключение
На момент написания первые 1 000 000 запусков функции Яндекс предоставлял бесплатно. С учетом того, что нам требуется всего несколько запусков в сутки, это очень удобное решение.
В целом, в статье описан крайне примитивный вариант использования и самих функций, и триггера на отправку. Можно модернизировать решение задачи, например, реализовать сохранение вложений в бакет, разбор вложения функций, более таргетированную рассылку в чаты и т.д.