Skip to content

Commit 0344a80

Browse files
ernadoclaude
andcommitted
feat(handler): add Context helpers (Message, Sender, Chat, Send, Reply)
Ergonomic accessors on the handler Context: Message (effective message), Sender (message/callback/inline-query user), Chat (target chat id). Send posts to the update's chat; Reply posts as a reply to the incoming message. Both delegate to SendMessage with the Context as the request context. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 4dcbe6d commit 0344a80

2 files changed

Lines changed: 99 additions & 0 deletions

File tree

context.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package botapi
2+
3+
// Message returns the message the update carries (new/edited message or channel
4+
// post), or nil.
5+
func (c *Context) Message() *Message { return c.Update.EffectiveMessage() }
6+
7+
// Sender returns the user who produced the update (message sender or callback/
8+
// inline-query sender), or nil when there is none.
9+
func (c *Context) Sender() *User {
10+
switch {
11+
case c.Update.CallbackQuery != nil:
12+
return &c.Update.CallbackQuery.From
13+
case c.Update.InlineQuery != nil:
14+
return &c.Update.InlineQuery.From
15+
}
16+
if m := c.Message(); m != nil {
17+
return m.From
18+
}
19+
return nil
20+
}
21+
22+
// Chat returns the target chat id of the update and whether one is present.
23+
func (c *Context) Chat() (ChatID, bool) {
24+
if m := c.Message(); m != nil {
25+
return ID(m.Chat.ID), true
26+
}
27+
if cq := c.Update.CallbackQuery; cq != nil && cq.Message != nil {
28+
return ID(cq.Message.Chat.ID), true
29+
}
30+
return nil, false
31+
}
32+
33+
// Send sends a text message to the update's chat.
34+
func (c *Context) Send(text string, opts ...SendOption) (*Message, error) {
35+
chat, ok := c.Chat()
36+
if !ok {
37+
return nil, &Error{Code: 400, Description: "Bad Request: update has no chat to send to"}
38+
}
39+
return c.Bot.SendMessage(c, chat, text, opts...)
40+
}
41+
42+
// Reply sends a text message to the update's chat as a reply to the incoming
43+
// message.
44+
func (c *Context) Reply(text string, opts ...SendOption) (*Message, error) {
45+
m := c.Message()
46+
if m == nil {
47+
return nil, &Error{Code: 400, Description: "Bad Request: update has no message to reply to"}
48+
}
49+
opts = append([]SendOption{ReplyTo(m.MessageID)}, opts...)
50+
return c.Bot.SendMessage(c, ID(m.Chat.ID), text, opts...)
51+
}

context_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package botapi
2+
3+
import (
4+
"context"
5+
"testing"
6+
)
7+
8+
func TestContextChatAndSender(t *testing.T) {
9+
c := &Context{
10+
Context: context.Background(),
11+
Update: &Update{Message: &Message{
12+
MessageID: 7,
13+
Chat: Chat{ID: -100500, Type: ChatTypeSupergroup},
14+
From: &User{ID: 11, Username: "ada"},
15+
}},
16+
}
17+
18+
chat, ok := c.Chat()
19+
if !ok {
20+
t.Fatal("Chat should be present")
21+
}
22+
if id, isInt := chat.(ChatIDInt); !isInt || int64(id) != -100500 {
23+
t.Fatalf("Chat id = %v", chat)
24+
}
25+
if s := c.Sender(); s == nil || s.ID != 11 {
26+
t.Fatalf("Sender = %+v", c.Sender())
27+
}
28+
}
29+
30+
func TestContextSenderFromCallback(t *testing.T) {
31+
c := &Context{
32+
Context: context.Background(),
33+
Update: &Update{CallbackQuery: &CallbackQuery{From: User{ID: 99}}},
34+
}
35+
if s := c.Sender(); s == nil || s.ID != 99 {
36+
t.Fatalf("callback sender = %+v", c.Sender())
37+
}
38+
}
39+
40+
func TestContextReplyWithoutMessage(t *testing.T) {
41+
c := &Context{Context: context.Background(), Bot: newTestBot(t), Update: &Update{InlineQuery: &InlineQuery{}}}
42+
if _, err := c.Reply("hi"); err == nil {
43+
t.Fatal("Reply with no message should error")
44+
}
45+
if _, ok := c.Chat(); ok {
46+
t.Fatal("inline-query-only update should have no chat")
47+
}
48+
}

0 commit comments

Comments
 (0)