text table, telegram html message

This commit is contained in:
2025-11-28 15:43:07 +03:00
parent 68310ac711
commit 5727c7a60c
6 changed files with 179 additions and 4 deletions

5
go.mod
View File

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

4
go.sum
View File

@@ -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=

View File

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

View File

@@ -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, "<pre>"+msg+"</pre>")
if err != nil {
t.Fatal(err)
}
t.Log(m)
}

112
tools.go Normal file
View File

@@ -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 <pre>
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()
}

20
tools_test.go Normal file
View File

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