refactor: чистка мёртвого кода, лимит ходов D3, HTML-экранирование и UTF-8 обрезка в Telegram
This commit is contained in:
@@ -24,7 +24,6 @@ type Router struct {
|
||||
// long-poll цикл канала (Telegram) не блокируется на время долгого
|
||||
// вызова аналитика и продолжает принимать новые сообщения.
|
||||
incoming chan Incoming
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewRouter создаёт роутер. onUserMsg — колбэк обработки входящего.
|
||||
@@ -47,7 +46,6 @@ func NewRouter(onUserMsg func(Incoming)) *Router {
|
||||
func (r *Router) processLoop() {
|
||||
for inc := range r.incoming {
|
||||
r.onUserMsg(inc)
|
||||
r.wg.Done()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +78,6 @@ func (r *Router) handleIncoming(inc Incoming) {
|
||||
|
||||
// Асинхронная обработка: кладём событие в очередь воркера и сразу
|
||||
// возвращаемся, не блокируя вызывающий long-poll цикл канала.
|
||||
r.wg.Add(1)
|
||||
r.incoming <- inc
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/kamelion/ratatoskr-go/internal/chat"
|
||||
)
|
||||
@@ -109,7 +110,7 @@ func (ch *Channel) handleUpdate(ctx context.Context, upd update) {
|
||||
func (ch *Channel) sendMsg(chatID, text string) error {
|
||||
body, _ := json.Marshal(map[string]string{
|
||||
"chat_id": chatID,
|
||||
"text": text[:min(len(text), 4000)],
|
||||
"text": truncateUTF8(text, 4000),
|
||||
"parse_mode": "HTML",
|
||||
})
|
||||
url := fmt.Sprintf(ch.apiURL+"sendMessage", ch.token)
|
||||
@@ -155,10 +156,10 @@ func (ch *Channel) getUpdates(ctx context.Context, offset int64, timeout int) ([
|
||||
// formatOutgoing собирает Message в HTML-строку: текст + нумерованные Options.
|
||||
func formatOutgoing(m chat.Message) string {
|
||||
if len(m.Options) == 0 {
|
||||
return m.Text
|
||||
return escapeHTML(m.Text)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(m.Text)
|
||||
buf.WriteString(escapeHTML(m.Text))
|
||||
buf.WriteString("\n\n")
|
||||
for i, opt := range m.Options {
|
||||
buf.WriteString(fmt.Sprintf("<b>%d.</b> %s\n", i+1, escapeHTML(opt.Label)))
|
||||
@@ -183,6 +184,18 @@ func escapeHTML(s string) string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// truncateUTF8 обрезает s до max байт, не разрывая UTF-8 последовательности.
|
||||
func truncateUTF8(s string, max int) string {
|
||||
if len(s) <= max {
|
||||
return s
|
||||
}
|
||||
s = s[:max]
|
||||
for len(s) > 0 && !utf8.ValidString(s) {
|
||||
s = s[:len(s)-1]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// ---- Telegram API types ----
|
||||
|
||||
type tgResponse struct {
|
||||
@@ -202,4 +215,4 @@ type message struct {
|
||||
Chat struct {
|
||||
ID int64 `json:"id"`
|
||||
} `json:"chat"`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/kamelion/ratatoskr-go/internal/chat"
|
||||
)
|
||||
@@ -111,6 +112,36 @@ func TestSendWithOptions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatOutgoingEscapesText(t *testing.T) {
|
||||
if got := formatOutgoing(chat.Message{Text: "2 < 3 & 4 > 1"}); got != "2 < 3 & 4 > 1" {
|
||||
t.Errorf("text escape = %q", got)
|
||||
}
|
||||
got := formatOutgoing(chat.Message{
|
||||
Text: "a<b",
|
||||
Options: []chat.Option{{ID: "x", Label: "l&l"}},
|
||||
})
|
||||
if !strings.Contains(got, "a<b") || !strings.Contains(got, "l&l") {
|
||||
t.Errorf("options escape = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTruncateUTF8(t *testing.T) {
|
||||
long := strings.Repeat("я", 5000)
|
||||
tr := truncateUTF8(long, 4000)
|
||||
if len(tr) != 4000 {
|
||||
t.Fatalf("len = %d, want 4000", len(tr))
|
||||
}
|
||||
if !utf8.ValidString(tr) {
|
||||
t.Fatal("truncated string is not valid UTF-8")
|
||||
}
|
||||
if got := truncateUTF8("привет", 4000); got != "привет" {
|
||||
t.Fatalf("short text changed: %q", got)
|
||||
}
|
||||
if got := truncateUTF8("", 4000); got != "" {
|
||||
t.Fatalf("empty text changed: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIncoming(t *testing.T) {
|
||||
f := newFakeTG(t)
|
||||
ch := New("TOKEN", time.Second)
|
||||
@@ -143,4 +174,4 @@ func TestIncoming(t *testing.T) {
|
||||
case <-ctx.Done():
|
||||
t.Fatal("timeout ожидания входящего")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user