Skip to content

Commit c01516b

Browse files
ernadoclaude
andcommitted
feat(answer): implement AnswerCallbackQuery
Respond to inline-keyboard callback queries via messages.setBotCallbackAnswer, with options for text, alert, URL and cache time. Add a Context.AnswerCallback helper for the handler framework. AnswerInlineQuery and the payment answers (PreCheckout/Shipping) are deferred: they need the InlineQueryResult union and payment update plumbing respectively, neither of which is wired yet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent cd6995b commit c01516b

3 files changed

Lines changed: 102 additions & 0 deletions

File tree

answer.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package botapi
2+
3+
import (
4+
"context"
5+
"strconv"
6+
7+
"github.com/gotd/td/tg"
8+
)
9+
10+
// AnswerCallbackQueryOption configures an AnswerCallbackQuery call.
11+
type AnswerCallbackQueryOption func(*answerCallbackConfig)
12+
13+
type answerCallbackConfig struct {
14+
text string
15+
showAlert bool
16+
url string
17+
cacheTime int
18+
}
19+
20+
// WithCallbackText sets the notification text shown to the user (0-200
21+
// characters). By default nothing is shown.
22+
func WithCallbackText(text string) AnswerCallbackQueryOption {
23+
return func(c *answerCallbackConfig) { c.text = text }
24+
}
25+
26+
// WithCallbackAlert shows the notification as an alert dialog instead of a
27+
// top-of-screen toast.
28+
func WithCallbackAlert() AnswerCallbackQueryOption {
29+
return func(c *answerCallbackConfig) { c.showAlert = true }
30+
}
31+
32+
// WithCallbackURL sets a URL to open. Telegram restricts which URLs are
33+
// accepted (game URLs, or t.me/your_bot?start= links).
34+
func WithCallbackURL(url string) AnswerCallbackQueryOption {
35+
return func(c *answerCallbackConfig) { c.url = url }
36+
}
37+
38+
// WithCallbackCacheTime sets, in seconds, how long the result may be cached
39+
// client-side.
40+
func WithCallbackCacheTime(seconds int) AnswerCallbackQueryOption {
41+
return func(c *answerCallbackConfig) { c.cacheTime = seconds }
42+
}
43+
44+
// AnswerCallbackQuery responds to a callback query sent from an inline keyboard.
45+
// The callbackQueryID is the CallbackQuery.ID from the update.
46+
func (b *Bot) AnswerCallbackQuery(ctx context.Context, callbackQueryID string, opts ...AnswerCallbackQueryOption) error {
47+
var cfg answerCallbackConfig
48+
for _, o := range opts {
49+
o(&cfg)
50+
}
51+
52+
queryID, err := strconv.ParseInt(callbackQueryID, 10, 64)
53+
if err != nil {
54+
return &Error{Code: 400, Description: "Bad Request: invalid callback query id"}
55+
}
56+
57+
req := &tg.MessagesSetBotCallbackAnswerRequest{
58+
Alert: cfg.showAlert,
59+
QueryID: queryID,
60+
CacheTime: cfg.cacheTime,
61+
}
62+
if cfg.text != "" {
63+
req.SetMessage(cfg.text)
64+
}
65+
if cfg.url != "" {
66+
req.SetURL(cfg.url)
67+
}
68+
69+
if _, err := b.raw.MessagesSetBotCallbackAnswer(ctx, req); err != nil {
70+
return asAPIError(err)
71+
}
72+
return nil
73+
}

answer_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package botapi
2+
3+
import (
4+
"context"
5+
"errors"
6+
"testing"
7+
)
8+
9+
func TestAnswerCallbackQueryInvalidID(t *testing.T) {
10+
b := &Bot{}
11+
err := b.AnswerCallbackQuery(context.Background(), "not-a-number")
12+
if err == nil {
13+
t.Fatal("expected error for non-numeric callback query id")
14+
}
15+
var apiErr *Error
16+
if !errors.As(err, &apiErr) || apiErr.Code != 400 {
17+
t.Fatalf("want 400 Error, got %v", err)
18+
}
19+
}

context.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,13 @@ func (c *Context) Reply(text string, opts ...SendOption) (*Message, error) {
4949
opts = append([]SendOption{ReplyTo(m.MessageID)}, opts...)
5050
return c.Bot.SendMessage(c, ID(m.Chat.ID), text, opts...)
5151
}
52+
53+
// AnswerCallback answers the update's callback query. It is an error to call it
54+
// when the update is not a callback query.
55+
func (c *Context) AnswerCallback(opts ...AnswerCallbackQueryOption) error {
56+
cq := c.Update.CallbackQuery
57+
if cq == nil {
58+
return &Error{Code: 400, Description: "Bad Request: update has no callback query to answer"}
59+
}
60+
return c.Bot.AnswerCallbackQuery(c, cq.ID, opts...)
61+
}

0 commit comments

Comments
 (0)