diff --git a/go.mod b/go.mod index 4cfd846..818761f 100644 --- a/go.mod +++ b/go.mod @@ -6,3 +6,8 @@ require ( github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 gopkg.in/yaml.v2 v2.4.0 ) + +require ( + github.com/clipperhouse/uax29/v2 v2.2.0 // indirect + github.com/mattn/go-runewidth v0.0.19 // indirect +) diff --git a/go.sum b/go.sum index c80f9bc..2da05c6 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ +github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY= +github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8= +github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= +github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/telegram.go b/telegram.go index ff5e275..816ec19 100644 --- a/telegram.go +++ b/telegram.go @@ -60,3 +60,9 @@ func (s *Bot) SendTextMessage(chatID int64, text string) (tgbotapi.Message, erro message.ParseMode = "Markdown" return s.api.Send(message) } + +func (s *Bot) SendHTMLMessage(chatID int64, text string) (tgbotapi.Message, error) { + message := tgbotapi.NewMessage(chatID, text) + message.ParseMode = "HTML" + return s.api.Send(message) +} diff --git a/telegram_test.go b/telegram_test.go index 43c98dd..93fdda6 100644 --- a/telegram_test.go +++ b/telegram_test.go @@ -1,7 +1,6 @@ package notify_test import ( - "log" "os" "testing" @@ -14,17 +13,19 @@ type ConfigTelegram struct { Token string `yaml:"token"` } -func TestMessage(t *testing.T) { +func loadConfig(t *testing.T) (conf ConfigTelegram) { cData, err := os.ReadFile("test_data/telegram.yaml") if err != nil { t.Fatal(err) } - var conf ConfigTelegram if err = yaml.Unmarshal(cData, &conf); err != nil { t.Fatal(err) } + return +} - log.Println(conf) +func TestMessage(t *testing.T) { + conf := loadConfig(t) bot, err := notify.NewTelegram(conf.Token, true) if err != nil { t.Fatal(err) @@ -35,3 +36,30 @@ func TestMessage(t *testing.T) { } t.Log(m) } + +func TestSendTable(t *testing.T) { + rows := [][]string{ + {"Название", "Цена", "Кол-во", "Рост"}, + {"Яблоки", "120", "5", "3.5"}, + {"Бананы", "-90", "30", "12 000"}, + {"Киви", "+200", "1", "-1.2"}, + {"Груши", "100", "10", "0.5"}, + {"Апельсины", "150", "10", "1.5"}, + {"Мандарины", "100", "10", "0.5"}, + {"Персики", "100", "10", "0.5"}, + {"Виноград", "100", "10", "0.5"}, + {"Абрикосы", "100", "10", "0.5"}, + {"Слива Абрикосы Абрикосы", "100", "10", "0.5"}, + } + + msg := notify.BuildTelegramHTMLTable(rows) + + conf := loadConfig(t) + bot, err := notify.NewTelegram(conf.Token, true) + + m, err := bot.SendHTMLMessage(conf.GroupID, "
"+msg+"
") + if err != nil { + t.Fatal(err) + } + t.Log(m) +} diff --git a/tools.go b/tools.go new file mode 100644 index 0000000..128b5e6 --- /dev/null +++ b/tools.go @@ -0,0 +1,112 @@ +package notify + +import ( + "html" + "regexp" + "strings" + + "github.com/mattn/go-runewidth" +) + +// Проверка, что строка — число (целое или с точкой, +,-, пробелы) +var numberRe = regexp.MustCompile(`^[+-]?[\d\s_]*\.?\d+$`) + +// Выравнивание текста по центру с учётом визуальной ширины +func centerPad(s string, width int) string { + w := runewidth.StringWidth(s) + if w >= width { + return s + } + left := (width - w) / 2 + right := width - w - left + return strings.Repeat(" ", left) + s + strings.Repeat(" ", right) +} + +// Выравнивание текста по левому краю с учётом ширины +func padRightVisual(s string, width int) string { + w := runewidth.StringWidth(s) + if w >= width { + return s + } + return s + strings.Repeat(" ", width-w) +} + +// Выравнивание текста по правому краю с учётом ширины +func padLeftVisual(s string, width int) string { + w := runewidth.StringWidth(s) + if w >= width { + return s + } + return strings.Repeat(" ", width-w) + s +} + +// BuildTelegramBoxTable строит ASCII-таблицу с рамками для Telegram HTML
+func BuildTelegramHTMLTable(rows [][]string) string {
+	if len(rows) == 0 {
+		return ""
+	}
+
+	// Экранируем HTML-символы
+	for i := range rows {
+		for j := range rows[i] {
+			rows[i][j] = html.EscapeString(rows[i][j])
+		}
+	}
+
+	// Определяем визуальную ширину колонок
+	colWidths := make([]int, len(rows[0]))
+	for _, row := range rows {
+		for i, col := range row {
+			w := runewidth.StringWidth(col)
+			if w > colWidths[i] {
+				colWidths[i] = w
+			}
+		}
+	}
+
+	// Функция для линий-разделителей
+	buildSep := func() string {
+		var s strings.Builder
+		s.WriteString("+")
+		for _, w := range colWidths {
+			s.WriteString(strings.Repeat("-", w+2))
+			s.WriteString("+")
+		}
+		return s.String()
+	}
+
+	var b strings.Builder
+	b.WriteString(buildSep() + "\n")
+
+	for ri, row := range rows {
+		b.WriteString("|")
+		for i, col := range row {
+			w := colWidths[i]
+			var cell string
+
+			switch {
+			case ri == 0:
+				// Заголовок — по центру
+				cell = centerPad(col, w)
+			case numberRe.MatchString(col):
+				// Число — по правому краю
+				cell = padLeftVisual(col, w)
+			default:
+				// Текст — по левому краю
+				cell = padRightVisual(col, w)
+			}
+
+			b.WriteString(" " + cell + " |")
+		}
+		b.WriteString("\n")
+
+		// Разделитель после заголовка
+		if ri == 0 {
+			b.WriteString(buildSep() + "\n")
+		}
+	}
+
+	b.WriteString(buildSep() + "\n")
+
+	return b.String()
+}
diff --git a/tools_test.go b/tools_test.go
new file mode 100644
index 0000000..2acfee8
--- /dev/null
+++ b/tools_test.go
@@ -0,0 +1,20 @@
+package notify_test
+
+import (
+	"log"
+	"testing"
+
+	"git.gm6.ru/icewind/notify"
+)
+
+func TestCreateTable(t *testing.T) {
+	rows := [][]string{
+		{"Название", "Цена", "Кол-во", "Рост"},
+		{"Яблоки", "120", "5", "3.5"},
+		{"Бананы", "-90", "30", "12 000"},
+		{"Киви", "+200", "1", "-1.2"},
+	}
+
+	msg := notify.BuildTelegramHTMLTable(rows)
+	log.Println(msg)
+}