Address Copilot review: handle HTML expansion exceeding Telegram limit
When markdownToTelegramHTML expands a chunk beyond 4096 chars (e.g. **a** → <b>a</b>), re-split the markdown with a proportionally smaller maxLen so each resulting HTML chunk fits within Telegram's limit. Extract sendHTMLChunk helper to avoid duplicating the HTML-send + plain-text-fallback logic. Add test case for markdown-short-but-HTML-long scenario to verify the re-splitting behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3501962977
commit
33109a1676
2 changed files with 61 additions and 9 deletions
|
|
@ -184,10 +184,38 @@ func (c *TelegramChannel) Send(ctx context.Context, msg bus.OutboundMessage) err
|
||||||
|
|
||||||
for _, chunk := range mdChunks {
|
for _, chunk := range mdChunks {
|
||||||
htmlContent := markdownToTelegramHTML(chunk)
|
htmlContent := markdownToTelegramHTML(chunk)
|
||||||
|
|
||||||
|
// If HTML expansion pushes the chunk over Telegram's 4096-char limit,
|
||||||
|
// re-split the markdown chunk with a proportionally smaller maxLen.
|
||||||
|
if len([]rune(htmlContent)) > 4096 {
|
||||||
|
ratio := float64(len([]rune(chunk))) / float64(len([]rune(htmlContent)))
|
||||||
|
smallerLen := int(float64(4096) * ratio * 0.95) // 5% safety margin
|
||||||
|
if smallerLen < 100 {
|
||||||
|
smallerLen = 100
|
||||||
|
}
|
||||||
|
subChunks := channels.SplitMessage(chunk, smallerLen)
|
||||||
|
for _, sub := range subChunks {
|
||||||
|
if err := c.sendHTMLChunk(ctx, chatID, markdownToTelegramHTML(sub)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.sendHTMLChunk(ctx, chatID, htmlContent); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendHTMLChunk sends a single HTML message, falling back to plain text on parse failure.
|
||||||
|
func (c *TelegramChannel) sendHTMLChunk(ctx context.Context, chatID int64, htmlContent string) error {
|
||||||
tgMsg := tu.Message(tu.ID(chatID), htmlContent)
|
tgMsg := tu.Message(tu.ID(chatID), htmlContent)
|
||||||
tgMsg.ParseMode = telego.ModeHTML
|
tgMsg.ParseMode = telego.ModeHTML
|
||||||
|
|
||||||
if _, err = c.bot.SendMessage(ctx, tgMsg); err != nil {
|
if _, err := c.bot.SendMessage(ctx, tgMsg); err != nil {
|
||||||
logger.ErrorCF("telegram", "HTML parse failed, falling back to plain text", map[string]any{
|
logger.ErrorCF("telegram", "HTML parse failed, falling back to plain text", map[string]any{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
|
|
@ -196,8 +224,6 @@ func (c *TelegramChannel) Send(ctx context.Context, msg bus.OutboundMessage) err
|
||||||
return fmt.Errorf("telegram send: %w", channels.ErrTemporary)
|
return fmt.Errorf("telegram send: %w", channels.ErrTemporary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -196,6 +196,32 @@ func TestSend_LongMessage_HTMLFallback_StopsOnError(t *testing.T) {
|
||||||
assert.Equal(t, 2, len(caller.calls), "should stop after first chunk fails both HTML and plain text")
|
assert.Equal(t, 2, len(caller.calls), "should stop after first chunk fails both HTML and plain text")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSend_MarkdownShortButHTMLLong_MultipleCalls(t *testing.T) {
|
||||||
|
caller := &stubCaller{
|
||||||
|
callFn: func(ctx context.Context, url string, data *ta.RequestData) (*ta.Response, error) {
|
||||||
|
return successResponse(t), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ch := newTestChannel(t, caller)
|
||||||
|
|
||||||
|
// Create markdown whose length is <= 4096 but whose HTML expansion is much longer.
|
||||||
|
// "**a**" (5 chars) becomes "<b>a</b>" (8 chars) in HTML, so repeating it many times
|
||||||
|
// yields HTML that exceeds Telegram's limit while markdown stays within it.
|
||||||
|
markdownContent := strings.Repeat("**a** ", 700) // ~4200 chars markdown, but HTML ~5600+ chars
|
||||||
|
assert.LessOrEqual(t, len([]rune(markdownContent)), 4200, "markdown content should be near Telegram limit")
|
||||||
|
|
||||||
|
htmlExpanded := markdownToTelegramHTML(markdownContent)
|
||||||
|
assert.Greater(t, len([]rune(htmlExpanded)), 4096, "HTML expansion must exceed Telegram limit for this test to be meaningful")
|
||||||
|
|
||||||
|
err := ch.Send(context.Background(), bus.OutboundMessage{
|
||||||
|
ChatID: "12345",
|
||||||
|
Content: markdownContent,
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Greater(t, len(caller.calls), 1, "markdown-short but HTML-long message should be split into multiple SendMessage calls")
|
||||||
|
}
|
||||||
|
|
||||||
func TestSend_NotRunning(t *testing.T) {
|
func TestSend_NotRunning(t *testing.T) {
|
||||||
caller := &stubCaller{
|
caller := &stubCaller{
|
||||||
callFn: func(ctx context.Context, url string, data *ta.RequestData) (*ta.Response, error) {
|
callFn: func(ctx context.Context, url string, data *ta.RequestData) (*ta.Response, error) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue