fix(telegram): refine duplicate-message protection with narrow error classification
Addresses reviewer concerns regarding silent message loss by narrowing the error swallowing logic in EditMessage: - Excludes context.DeadlineExceeded and context.Canceled from being swallowed, ensuring local timeouts before transmission still trigger a fallback send. - Adds an explicit check for the 'message is not modified' error to safely identify edits that have already landed on Telegram's servers. - Narrowly targets confirmed post-connect dropouts (e.g., connection reset) instead of broad network-ish string matching. - Fixes the missing isPostConnectError definition and required errors import.
This commit is contained in:
parent
dd54601f2d
commit
b90a6d12ea
1 changed files with 57 additions and 2 deletions
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
@ -377,8 +378,38 @@ func (c *TelegramChannel) EditMessage(ctx context.Context, chatID string, messag
|
||||||
}
|
}
|
||||||
_, err = c.bot.EditMessageText(ctx, editMsg)
|
_, err = c.bot.EditMessageText(ctx, editMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logParseFailed(err, useMarkdownV2)
|
// If it failed because it was already modified (likely from a previous
|
||||||
_, err = c.bot.EditMessageText(ctx, tu.EditMessageText(tu.ID(cid), mid, content))
|
// attempt that timed out on our end but landed on Telegram), we treat
|
||||||
|
// it as success to prevent the Manager from sending a duplicate message.
|
||||||
|
if strings.Contains(err.Error(), "message is not modified") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only fallback to plain text if the error looks like a parsing failure (Bad Request).
|
||||||
|
// Network errors or timeouts should NOT trigger a retry with different content.
|
||||||
|
if strings.Contains(err.Error(), "Bad Request") {
|
||||||
|
logParseFailed(err, useMarkdownV2)
|
||||||
|
_, err = c.bot.EditMessageText(ctx, tu.EditMessageText(tu.ID(cid), mid, content))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "message is not modified") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if isPostConnectError(err) {
|
||||||
|
logger.WarnCF(
|
||||||
|
"telegram",
|
||||||
|
"EditMessage likely landed but result is unknown; swallowing error to prevent duplicate",
|
||||||
|
map[string]any{
|
||||||
|
"chat_id": chatID,
|
||||||
|
"mid": mid,
|
||||||
|
"error": err.Error(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return nil // Swallow to prevent Manager fallback to a new SendMessage
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|
@ -1133,3 +1164,27 @@ func cryptoRandInt() int {
|
||||||
_, _ = rand.Read(b[:])
|
_, _ = rand.Read(b[:])
|
||||||
return int(binary.BigEndian.Uint32(b[:])) | 1 // ensure non-zero
|
return int(binary.BigEndian.Uint32(b[:])) | 1 // ensure non-zero
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isPostConnectError identifies network errors that likely occurred after
|
||||||
|
// the request was transmitted to Telegram (e.g. dropped connection while
|
||||||
|
// waiting for response). Swallowing these for edits prevents duplicate
|
||||||
|
// fallbacks, at the small risk of leaving a stale placeholder if the
|
||||||
|
// edit never actually reached the server.
|
||||||
|
func isPostConnectError(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context errors (timeout/canceled) are too broad; they can be triggered
|
||||||
|
// locally before any data is sent. Never swallow them.
|
||||||
|
if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := strings.ToLower(err.Error())
|
||||||
|
// Narrowly target connection dropouts where the request likely landed.
|
||||||
|
return strings.Contains(msg, "connection reset by peer") ||
|
||||||
|
strings.Contains(msg, "unexpected eof") ||
|
||||||
|
strings.Contains(msg, "connection closed by foreign host") ||
|
||||||
|
strings.Contains(msg, "broken pipe")
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue