diff --git a/.gitignore b/.gitignore index fb7f0c4..5ab7f0b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -test_data \ No newline at end of file +test_data +CLAUDE.md \ No newline at end of file diff --git a/README.md b/README.md index 2cde880..7c30d52 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,136 @@ # notify +Go-библиотека для отправки уведомлений через Telegram и Email. + +## Установка + +```bash +go get git.gm6.ru/icewind/notify +``` + +## Telegram + +### Создание бота + +```go +bot, err := notify.NewTelegram("BOT_TOKEN", true) // true — отключить IPv6 +if err != nil { + log.Fatal(err) +} +``` + +Второй параметр `disableIPV6` принудительно использует TCP4 для соединений с Telegram API — полезно в средах, где IPv6 недоступен или работает нестабильно. + +### Отправка текстовых сообщений + +```go +// Markdown +bot.SendTextMessage(chatID, "*Жирный* и _курсив_") + +// HTML +bot.SendHTMLMessage(chatID, "Жирный и курсив") +``` + +### Отправка фото + +```go +// Из файла +bot.SendPhotoFromFile(chatID, "/path/to/image.png", "Подпись") + +// По URL +bot.SendPhotoFromURL(chatID, "https://example.com/image.png", "Подпись") + +// Из байтов +bot.SendPhotoFromBytes(chatID, imageData, "photo.png", "Подпись") +``` + +### Отправка документов + +```go +// Из файла +bot.SendDocumentFromFile(chatID, "/path/to/report.pdf", "Отчёт за месяц") + +// По URL +bot.SendDocumentFromURL(chatID, "https://example.com/report.pdf", "Отчёт") + +// Из байтов +bot.SendDocumentFromBytes(chatID, fileData, "report.pdf", "Отчёт") +``` + +### Доступ к нативному API + +Для операций, не покрытых обёрткой, можно получить оригинальный `*tgbotapi.BotAPI`: + +```go +api := bot.GetAPI() +updates, _ := api.GetUpdates(tgbotapi.NewUpdate(0)) +``` + +## Таблицы для Telegram + +`BuildTelegramTable` формирует ASCII-таблицу с рамками, готовую для отправки в `
` блоке. Заголовки выравниваются по центру, числа — по правому краю, текст — по левому. Корректно работает с кириллицей и другими широкими символами.
+
+```go
+rows := [][]string{
+ {"Товар", "Цена", "Кол-во"},
+ {"Яблоки", "120", "5"},
+ {"Бананы", "-90", "30"},
+ {"Киви", "+200", "1"},
+}
+
+table := notify.BuildTelegramTable(rows)
+bot.SendHTMLMessage(chatID, "" + table + "
")
+```
+
+Результат в Telegram:
+
+```
++--------+------+--------+
+| Товар | Цена | Кол-во |
++--------+------+--------+
+| Яблоки | 120 | 5 |
+| Бананы | -90 | 30 |
+| Киви | +200 | 1 |
++--------+------+--------+
+```
+
+## Email
+
+### Отправка HTML-письма
+
+```go
+auth := notify.SmtpAuth{
+ Addr: "smtp.example.com:587",
+ Auth: smtp.PlainAuth("", "user@example.com", "password", "smtp.example.com"),
+}
+
+from := mail.Address{Name: "Система", Address: "noreply@example.com"}
+to := mail.Address{Name: "Иванов", Address: "ivanov@example.com"}
+
+err := notify.SendEmailHTML(auth, "Отчёт
Всё в порядке
", "Тема письма", from, to)
+```
+
+Можно указать несколько получателей:
+
+```go
+notify.SendEmailHTML(auth, body, subject, from, recipient1, recipient2, recipient3)
+```
+
+### Отправка с вложениями
+
+Для писем с вложениями или расширенной настройкой используйте `SenndEmailMessage` с объектом `email.Message`:
+
+```go
+import "github.com/scorredoira/email"
+
+mes := email.NewMessage("Тема", "Текст письма")
+mes.From = from
+mes.AddTo(to)
+mes.AttachFile("/path/to/file.xlsx")
+
+err := notify.SenndEmailMessage(auth, mes)
+```
+
+## Лицензия
+
+MIT
diff --git a/telegram.go b/telegram.go
index ca2cced..2e12ba1 100644
--- a/telegram.go
+++ b/telegram.go
@@ -2,7 +2,6 @@ package notify
import (
"context"
- "log"
"net"
"net/http"
"time"
@@ -21,12 +20,14 @@ func NewTelegram(token string, disableIPV6 bool) (*Bot, error) {
}
transport := &http.Transport{
- // Заменяем стандартный DialContext
- DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
+ TLSHandshakeTimeout: 10 * time.Second,
+ }
+
+ if disableIPV6 {
+ transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
// "tcp4" отключает IPv6
return dialer.DialContext(ctx, "tcp4", addr)
- },
- TLSHandshakeTimeout: 10 * time.Second,
+ }
}
client := &http.Client{
@@ -34,20 +35,9 @@ func NewTelegram(token string, disableIPV6 bool) (*Bot, error) {
Timeout: 60 * time.Second,
}
- if disableIPV6 {
- client.Transport = &http.Transport{
- // Заменяем стандартный DialContext
- DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
- // "tcp4" отключает IPv6
- return dialer.DialContext(ctx, "tcp4", addr)
- },
- TLSHandshakeTimeout: 10 * time.Second,
- }
- }
-
botApi, err := tgbotapi.NewBotAPIWithClient(token, tgbotapi.APIEndpoint, client)
if err != nil {
- log.Fatalf("Telegram bot api error: %v", err)
+ return nil, err
}
bot := &Bot{
api: botApi,
diff --git a/tools.go b/tools.go
index 8ada29f..b3960b8 100644
--- a/tools.go
+++ b/tools.go
@@ -53,8 +53,23 @@ func BuildTelegramTable(rows [][]string) string {
}
}
+ // Определяем число колонок по максимальной строке
+ numCols := len(rows[0])
+ for _, row := range rows {
+ if len(row) > numCols {
+ numCols = len(row)
+ }
+ }
+
+ // Дополняем короткие строки пустыми ячейками
+ for i := range rows {
+ for len(rows[i]) < numCols {
+ rows[i] = append(rows[i], "")
+ }
+ }
+
// Определяем визуальную ширину колонок
- colWidths := make([]int, len(rows[0]))
+ colWidths := make([]int, numCols)
for _, row := range rows {
for i, col := range row {
w := runewidth.StringWidth(col)