Merge pull request #56 from dj-oyu/fix/telegram-rate-limit

fix: prevent Telegram 429 rate limit errors during PDF OCR
This commit is contained in:
dj-oyu 2026-03-20 04:20:38 +09:00 committed by GitHub
commit f1705d8ce4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 25 additions and 8 deletions

View file

@ -372,7 +372,7 @@ func (al *AgentLoop) processingIndicator(ctx context.Context, channel, chatID, l
}
go func() {
ticker := time.NewTicker(150 * time.Millisecond)
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop()
frame := 0
@ -596,8 +596,10 @@ func (al *AgentLoop) ocrPDF(
return fmt.Sprintf("[file:%s]", pdfPath)
}
// Track progress via stderr
// Track progress via stderr, keep last lines for error diagnosis
page := 0
var stderrTail []string
const maxTailLines = 20
scanner := bufio.NewScanner(stderrPipe)
for scanner.Scan() {
line := scanner.Text()
@ -605,12 +607,17 @@ func (al *AgentLoop) ocrPDF(
page++
indicator.UpdateLabel(fmt.Sprintf("%s (%d/%s)...", modeLabel, page, totalStr))
}
stderrTail = append(stderrTail, line)
if len(stderrTail) > maxTailLines {
stderrTail = stderrTail[1:]
}
}
if waitErr := cmd.Wait(); waitErr != nil {
logger.WarnCF("agent", "OCR command failed", map[string]any{
"path": pdfPath,
"error": waitErr.Error(),
"path": pdfPath,
"error": waitErr.Error(),
"stderr": strings.Join(stderrTail, "\n"),
})
return fmt.Sprintf("[file:%s]", pdfPath)
}

View file

@ -18,7 +18,8 @@ const (
// for the same status/task bubble. EditMessage APIs are more rate-sensitive
// than SendMessageDraft, so we throttle edits to avoid "(edited)" flicker
// and API rate limit errors. Draft-based channels bypass this throttle.
statusEditInterval = 500 * time.Millisecond
// Telegram enforces ~20 messages/min per group chat, so 3s is safe.
statusEditInterval = 3 * time.Second
)
// statusMsgEntry tracks a status or task message ID for later editing.
@ -85,8 +86,9 @@ func (m *Manager) handleStatusSend(ctx context.Context, name string, w *channelW
if v, loaded := m.placeholders.Load(key); loaded {
if entry, ok := v.(placeholderEntry); ok && entry.id != "" {
if editor, ok := w.ch.(MessageEditor); ok {
// Always record edit time to prevent retry storms on 429 errors.
m.statusEditTimes.Store(key, time.Now())
if err := editor.EditMessage(ctx, msg.ChatID, entry.id, msg.Content); err == nil {
m.statusEditTimes.Store(key, time.Now())
return
}
}
@ -97,8 +99,8 @@ func (m *Manager) handleStatusSend(ctx context.Context, name string, w *channelW
if v, loaded := m.statusMsgIDs.Load(key); loaded {
if entry, ok := v.(statusMsgEntry); ok && entry.messageID != "" {
if editor, ok := w.ch.(MessageEditor); ok {
m.statusEditTimes.Store(key, time.Now())
if err := editor.EditMessage(ctx, msg.ChatID, entry.messageID, msg.Content); err == nil {
m.statusEditTimes.Store(key, time.Now())
return
}
}
@ -223,8 +225,8 @@ func (m *Manager) handleTaskStatusSend(ctx context.Context, name string, w *chan
if v, loaded := m.taskMsgIDs.Load(taskKey); loaded {
if entry, ok := v.(statusMsgEntry); ok && entry.messageID != "" {
if editor, ok := w.ch.(MessageEditor); ok {
m.statusEditTimes.Store(taskKey, time.Now())
if err := editor.EditMessage(ctx, msg.ChatID, entry.messageID, msg.Content); err == nil {
m.statusEditTimes.Store(taskKey, time.Now())
return
}
}

View file

@ -305,6 +305,10 @@ func (c *TelegramChannel) sendChunk(
}
if _, err := c.bot.SendMessage(ctx, tgMsg); err != nil {
// Don't retry on rate limit errors — they aren't parse failures.
if strings.Contains(err.Error(), "429") || strings.Contains(err.Error(), "Too Many Requests") {
return fmt.Errorf("telegram send: %w", channels.ErrTemporary)
}
logParseFailed(err, params.useMarkdownV2)
tgMsg.Text = params.mdFallback
@ -372,6 +376,10 @@ func (c *TelegramChannel) EditMessage(ctx context.Context, chatID string, messag
}
_, err = c.bot.EditMessageText(ctx, editMsg)
if err != nil {
// Don't retry on rate limit errors — they aren't parse failures.
if strings.Contains(err.Error(), "429") || strings.Contains(err.Error(), "Too Many Requests") {
return err
}
logParseFailed(err, useMarkdownV2)
_, err = c.bot.EditMessageText(ctx, tu.EditMessageText(tu.ID(cid), mid, content))
}