Skip to content

Commit fb31c22

Browse files
committed
fix(bot): skip deleting absent Telegram webhook
Inspect webhook state before cleanup so long-polling startup does not fail on an empty deleteWebhook response. Fixes #52
1 parent 24f9dc6 commit fb31c22

2 files changed

Lines changed: 43 additions & 5 deletions

File tree

internal/bot/bot.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,8 @@ func New(ctx context.Context, cfg *config.Config, client *transmission.Transmiss
6767
}
6868
b.API = api
6969

70-
// Remove any stale webhook so long polling receives updates reliably
71-
if _, err := api.DeleteWebhook(ctx, &tgbot.DeleteWebhookParams{
72-
DropPendingUpdates: false,
73-
}); err != nil {
74-
return nil, fmt.Errorf("delete stale Telegram webhook: %w", err)
70+
if err := clearWebhook(ctx, api); err != nil {
71+
return nil, err
7572
}
7673

7774
me, err := api.GetMe(ctx)
@@ -91,6 +88,21 @@ func New(ctx context.Context, cfg *config.Config, client *transmission.Transmiss
9188
return b, nil
9289
}
9390

91+
func clearWebhook(ctx context.Context, api *tgbot.Bot) error {
92+
info, err := api.GetWebhookInfo(ctx)
93+
if err != nil {
94+
return fmt.Errorf("get Telegram webhook info: %w", err)
95+
}
96+
if info.URL == "" {
97+
return nil
98+
}
99+
100+
if _, err := api.DeleteWebhook(ctx, &tgbot.DeleteWebhookParams{DropPendingUpdates: false}); err != nil {
101+
return fmt.Errorf("delete stale Telegram webhook: %w", err)
102+
}
103+
return nil
104+
}
105+
94106
// defaultBotCommands returns the list of primary commands to display in Telegram's menu.
95107
func defaultBotCommands() []models.BotCommand {
96108
return []models.BotCommand{

internal/bot/bot_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"bytes"
55
"context"
66
"log/slog"
7+
"net/http"
8+
"net/http/httptest"
79
"strings"
810
"sync/atomic"
911
"testing"
@@ -162,6 +164,30 @@ func TestDefaultBotCommands(t *testing.T) {
162164
}
163165
}
164166

167+
func TestClearWebhookSkipsDeleteWhenUnset(t *testing.T) {
168+
var deleteCalls atomic.Int32
169+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
170+
w.Header().Set("Content-Type", "application/json")
171+
if strings.HasSuffix(r.URL.Path, "/getWebhookInfo") {
172+
_, _ = w.Write([]byte(`{"ok":true,"result":{"url":"","has_custom_certificate":false,"pending_update_count":0}}`))
173+
return
174+
}
175+
deleteCalls.Add(1)
176+
}))
177+
defer server.Close()
178+
179+
api, err := tgbot.New(testBotToken, tgbot.WithServerURL(server.URL), tgbot.WithSkipGetMe())
180+
if err != nil {
181+
t.Fatalf("create test bot: %v", err)
182+
}
183+
if err := clearWebhook(context.Background(), api); err != nil {
184+
t.Fatalf("clear absent webhook: %v", err)
185+
}
186+
if got := deleteCalls.Load(); got != 0 {
187+
t.Fatalf("deleteWebhook called %d times for an absent webhook", got)
188+
}
189+
}
190+
165191
func TestHelpCoversEveryMenuCommand(t *testing.T) {
166192
for _, command := range defaultBotCommands() {
167193
if !strings.Contains(config.Help, "/"+command.Command) {

0 commit comments

Comments
 (0)