исправление багов, документация
- telegram: флаг disableIPV6 не работал — IPv6 был отключён всегда - telegram: log.Fatalf заменён на возврат ошибки - tools: BuildTelegramTable не падает при строках разной длины - README.md с примерами использования - .gitignore обновлён
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
test_data
|
test_data
|
||||||
|
CLAUDE.md
|
||||||
134
README.md
134
README.md
@@ -1,2 +1,136 @@
|
|||||||
# notify
|
# 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, "<b>Жирный</b> и <i>курсив</i>")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Отправка фото
|
||||||
|
|
||||||
|
```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-таблицу с рамками, готовую для отправки в `<pre>` блоке. Заголовки выравниваются по центру, числа — по правому краю, текст — по левому. Корректно работает с кириллицей и другими широкими символами.
|
||||||
|
|
||||||
|
```go
|
||||||
|
rows := [][]string{
|
||||||
|
{"Товар", "Цена", "Кол-во"},
|
||||||
|
{"Яблоки", "120", "5"},
|
||||||
|
{"Бананы", "-90", "30"},
|
||||||
|
{"Киви", "+200", "1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
table := notify.BuildTelegramTable(rows)
|
||||||
|
bot.SendHTMLMessage(chatID, "<pre>" + table + "</pre>")
|
||||||
|
```
|
||||||
|
|
||||||
|
Результат в 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, "<h1>Отчёт</h1><p>Всё в порядке</p>", "Тема письма", 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
|
||||||
|
|||||||
24
telegram.go
24
telegram.go
@@ -2,7 +2,6 @@ package notify
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
@@ -21,12 +20,14 @@ func NewTelegram(token string, disableIPV6 bool) (*Bot, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
transport := &http.Transport{
|
transport := &http.Transport{
|
||||||
// Заменяем стандартный DialContext
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
}
|
||||||
|
|
||||||
|
if disableIPV6 {
|
||||||
|
transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
// "tcp4" отключает IPv6
|
// "tcp4" отключает IPv6
|
||||||
return dialer.DialContext(ctx, "tcp4", addr)
|
return dialer.DialContext(ctx, "tcp4", addr)
|
||||||
},
|
}
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
@@ -34,20 +35,9 @@ func NewTelegram(token string, disableIPV6 bool) (*Bot, error) {
|
|||||||
Timeout: 60 * time.Second,
|
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)
|
botApi, err := tgbotapi.NewBotAPIWithClient(token, tgbotapi.APIEndpoint, client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Telegram bot api error: %v", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
bot := &Bot{
|
bot := &Bot{
|
||||||
api: botApi,
|
api: botApi,
|
||||||
|
|||||||
17
tools.go
17
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 _, row := range rows {
|
||||||
for i, col := range row {
|
for i, col := range row {
|
||||||
w := runewidth.StringWidth(col)
|
w := runewidth.StringWidth(col)
|
||||||
|
|||||||
Reference in New Issue
Block a user