Skip to content

Commit c98b614

Browse files
committed
fix: remove approval inline keyboard after user responds; show user decision inline
1 parent d759f2d commit c98b614

2 files changed

Lines changed: 43 additions & 24 deletions

File tree

internal/telegram/approver.go

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ const (
2727

2828
// ── TelegramApprover ───────────────────────────────────────────────────────
2929

30+
// pendingRequest holds the response channel and the message ID for a
31+
// pending approval request, so HandleCallback can edit the message
32+
// text and remove the inline keyboard after the user responds.
33+
type pendingRequest struct {
34+
resp chan string
35+
messageID int
36+
}
37+
3038
// TelegramApprover implements danger.Approver by sending approval requests
3139
// via Telegram inline keyboards. The agent loop calls PromptCommand which:
3240
//
@@ -41,7 +49,7 @@ const (
4149
// Thread-safe: PromptCommand and HandleCallback are safe to call concurrently.
4250
type TelegramApprover struct {
4351
bot *Bot
44-
pending map[string]chan string // requestID -> response channel
52+
pending map[string]*pendingRequest // requestID -> pending request
4553
mu sync.Mutex
4654
trusted map[danger.RiskClass]bool
4755
trustAll bool // when true, all PromptCommand calls auto-approve
@@ -57,7 +65,7 @@ func NewTelegramApprover(bot *Bot, chatID int64) *TelegramApprover {
5765
return &TelegramApprover{
5866
bot: bot,
5967
ChatID: chatID,
60-
pending: make(map[string]chan string),
68+
pending: make(map[string]*pendingRequest),
6169
trusted: make(map[danger.RiskClass]bool),
6270
log: NewNopLogger(),
6371
cancel: make(chan struct{}),
@@ -129,18 +137,18 @@ func (a *TelegramApprover) PromptCommand(cls danger.RiskClass, cmd, description
129137
},
130138
}
131139

132-
_, err := a.bot.SendMessage(a.ChatID, text, &SendOpts{
140+
msg, err := a.bot.SendMessage(a.ChatID, text, &SendOpts{
133141
ParseMode: ParseModeMarkdownV2,
134142
ReplyMarkup: &markup,
135143
})
136144
if err != nil {
137145
return fmt.Errorf("telegram approver: send prompt: %w", err)
138146
}
139147

140-
// Register the pending request.
141-
resp := make(chan string, 1)
148+
// Register the pending request with message ID.
149+
pr := &pendingRequest{resp: make(chan string, 1), messageID: msg.ID}
142150
a.mu.Lock()
143-
a.pending[id] = resp
151+
a.pending[id] = pr
144152
a.mu.Unlock()
145153

146154
defer func() {
@@ -151,20 +159,31 @@ func (a *TelegramApprover) PromptCommand(cls danger.RiskClass, cmd, description
151159

152160
// Wait for response, cancellation, or timeout.
153161
select {
154-
case action := <-resp:
162+
case action := <-pr.resp:
163+
// Edit the approval message to remove buttons and show the user's decision.
164+
answerText := ""
165+
switch action {
166+
case "approve":
167+
answerText = "✅ *Approved*"
168+
case "trust":
169+
answerText = fmt.Sprintf("🔒 *Trusted:* `%s`", cls)
170+
case "deny":
171+
answerText = "❌ *Denied*"
172+
default:
173+
answerText = "⏭ *Skipped*"
174+
}
175+
if answerText != "" {
176+
a.bot.EditMessageText(a.ChatID, pr.messageID, answerText,
177+
&SendOpts{ParseMode: ParseModeMarkdownV2, ReplyMarkup: &InlineKeyboardMarkup{InlineKeyboard: [][]InlineKeyboardButton{}}})
178+
}
179+
155180
switch action {
156181
case "approve":
157182
return nil
158183
case "trust":
159184
a.mu.Lock()
160185
a.trusted[cls] = true
161186
a.mu.Unlock()
162-
// Confirm trust to the user
163-
if _, err := a.bot.SendMessage(a.ChatID,
164-
fmt.Sprintf("🔒 Class `%s` trusted for this session.", cls),
165-
&SendOpts{ParseMode: ParseModeMarkdownV2}); err != nil {
166-
a.log.Error("confirm trust message failed", "chat_id", a.ChatID, "error", err)
167-
}
168187
return nil
169188
case "deny":
170189
return fmt.Errorf("operation denied by user: %s", cmd)
@@ -208,11 +227,11 @@ func (a *TelegramApprover) HandleCallback(data string) bool {
208227
}
209228

210229
a.mu.Lock()
211-
resp, ok := a.pending[id]
230+
pr, ok := a.pending[id]
212231
a.mu.Unlock()
213232

214233
if ok {
215-
resp <- action
234+
pr.resp <- action
216235
}
217236

218237
return true

internal/telegram/approver_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ func TestHandleCallback_Approve(t *testing.T) {
4444
id := a.newID()
4545

4646
// Register a pending request manually.
47-
resp := make(chan string, 1)
48-
a.pending[id] = resp
47+
pr := &pendingRequest{resp: make(chan string, 1)}
48+
a.pending[id] = pr
4949

5050
// Handle an approve callback.
5151
handled := a.HandleCallback(cbPrefixApprove + id)
@@ -54,7 +54,7 @@ func TestHandleCallback_Approve(t *testing.T) {
5454
}
5555

5656
// Check the response channel received the action.
57-
action := <-resp
57+
action := <-pr.resp
5858
if action != "approve" {
5959
t.Errorf("response action = %q, want %q", action, "approve")
6060
}
@@ -68,15 +68,15 @@ func TestHandleCallback_Deny(t *testing.T) {
6868
a := NewTelegramApprover(bot, 1)
6969
id := a.newID()
7070

71-
resp := make(chan string, 1)
72-
a.pending[id] = resp
71+
pr := &pendingRequest{resp: make(chan string, 1)}
72+
a.pending[id] = pr
7373

7474
handled := a.HandleCallback(cbPrefixDeny + id)
7575
if !handled {
7676
t.Fatal("HandleCallback should return true for deny callback")
7777
}
7878

79-
action := <-resp
79+
action := <-pr.resp
8080
if action != "deny" {
8181
t.Errorf("response action = %q, want %q", action, "deny")
8282
}
@@ -90,15 +90,15 @@ func TestHandleCallback_Trust(t *testing.T) {
9090
a := NewTelegramApprover(bot, 1)
9191
id := a.newID()
9292

93-
resp := make(chan string, 1)
94-
a.pending[id] = resp
93+
pr := &pendingRequest{resp: make(chan string, 1)}
94+
a.pending[id] = pr
9595

9696
handled := a.HandleCallback(cbPrefixTrust + id)
9797
if !handled {
9898
t.Fatal("HandleCallback should return true for trust callback")
9999
}
100100

101-
action := <-resp
101+
action := <-pr.resp
102102
if action != "trust" {
103103
t.Errorf("response action = %q, want %q", action, "trust")
104104
}

0 commit comments

Comments
 (0)