fix(telegram): preserve reply context without breaking commands (#2197)
This commit is contained in:
parent
7b3f47128f
commit
5f07db934b
3 changed files with 234 additions and 0 deletions
|
|
@ -659,6 +659,9 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, message *telego.Mes
|
||||||
}
|
}
|
||||||
content = cleaned
|
content = cleaned
|
||||||
}
|
}
|
||||||
|
if quoted := formatQuotedReply(message); quoted != "" && !isCommandMessage(content) {
|
||||||
|
content = fmt.Sprintf("%s\n\n%s", quoted, content)
|
||||||
|
}
|
||||||
|
|
||||||
// For forum topics, embed the thread ID as "chatID/threadID" so replies
|
// For forum topics, embed the thread ID as "chatID/threadID" so replies
|
||||||
// route to the correct topic and each topic gets its own session.
|
// route to the correct topic and each topic gets its own session.
|
||||||
|
|
@ -693,6 +696,9 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, message *telego.Mes
|
||||||
"first_name": user.FirstName,
|
"first_name": user.FirstName,
|
||||||
"is_group": fmt.Sprintf("%t", message.Chat.Type != "private"),
|
"is_group": fmt.Sprintf("%t", message.Chat.Type != "private"),
|
||||||
}
|
}
|
||||||
|
if message.ReplyToMessage != nil {
|
||||||
|
metadata["reply_to_message_id"] = fmt.Sprintf("%d", message.ReplyToMessage.MessageID)
|
||||||
|
}
|
||||||
|
|
||||||
// Set parent_peer metadata for per-topic agent binding.
|
// Set parent_peer metadata for per-topic agent binding.
|
||||||
if message.Chat.IsForum && threadID != 0 {
|
if message.Chat.IsForum && threadID != 0 {
|
||||||
|
|
@ -713,6 +719,36 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, message *telego.Mes
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func formatQuotedReply(message *telego.Message) string {
|
||||||
|
if message == nil || message.ReplyToMessage == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
reply := message.ReplyToMessage
|
||||||
|
refContent := strings.TrimSpace(reply.Text)
|
||||||
|
if refContent == "" {
|
||||||
|
refContent = strings.TrimSpace(reply.Caption)
|
||||||
|
}
|
||||||
|
if refContent == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
refAuthor := "unknown"
|
||||||
|
if reply.From != nil {
|
||||||
|
if username := strings.TrimSpace(reply.From.Username); username != "" {
|
||||||
|
refAuthor = username
|
||||||
|
} else if firstName := strings.TrimSpace(reply.From.FirstName); firstName != "" {
|
||||||
|
refAuthor = firstName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("[quoted message from %s]: %s", refAuthor, refContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCommandMessage(content string) bool {
|
||||||
|
return commands.HasCommandPrefix(content)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *TelegramChannel) downloadPhoto(ctx context.Context, fileID string) string {
|
func (c *TelegramChannel) downloadPhoto(ctx context.Context, fileID string) string {
|
||||||
file, err := c.bot.GetFile(ctx, &telego.GetFileParams{FileID: fileID})
|
file, err := c.bot.GetFile(ctx, &telego.GetFileParams{FileID: fileID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -147,3 +147,53 @@ func TestIsBotMentioned_MentionEntityUnaffected(t *testing.T) {
|
||||||
t.Fatal("expected mention entity to be treated as bot mention")
|
t.Fatal("expected mention entity to be treated as bot mention")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHandleMessage_GroupReplyCommandKeepsCommandPrefixAndMetadata(t *testing.T) {
|
||||||
|
ch, messageBus := newGroupMentionOnlyChannel(t, "testbot")
|
||||||
|
|
||||||
|
msg := &telego.Message{
|
||||||
|
Text: "/new@testbot",
|
||||||
|
Entities: []telego.MessageEntity{{
|
||||||
|
Type: telego.EntityTypeBotCommand,
|
||||||
|
Offset: 0,
|
||||||
|
Length: len([]rune("/new@testbot")),
|
||||||
|
}},
|
||||||
|
MessageID: 43,
|
||||||
|
Chat: telego.Chat{
|
||||||
|
ID: 123,
|
||||||
|
Type: "group",
|
||||||
|
},
|
||||||
|
From: &telego.User{
|
||||||
|
ID: 7,
|
||||||
|
FirstName: "Alice",
|
||||||
|
},
|
||||||
|
ReplyToMessage: &telego.Message{
|
||||||
|
MessageID: 101,
|
||||||
|
Text: "old context",
|
||||||
|
From: &telego.User{
|
||||||
|
Username: "bob",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ch.handleMessage(context.Background(), msg); err != nil {
|
||||||
|
t.Fatalf("handleMessage error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||||
|
defer cancel()
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
t.Fatal("timeout waiting for message to be forwarded")
|
||||||
|
case inbound, ok := <-messageBus.InboundChan():
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected inbound message to be forwarded")
|
||||||
|
}
|
||||||
|
if inbound.Content != "/new" {
|
||||||
|
t.Fatalf("content=%q want=%q", inbound.Content, "/new")
|
||||||
|
}
|
||||||
|
if got := inbound.Metadata["reply_to_message_id"]; got != "101" {
|
||||||
|
t.Fatalf("reply_to_message_id=%q want=%q", got, "101")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -673,3 +673,151 @@ func TestHandleMessage_EmptyContent_Ignored(t *testing.T) {
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHandleMessage_ReplyCarriesMetadataAndQuotedContext(t *testing.T) {
|
||||||
|
messageBus := bus.NewMessageBus()
|
||||||
|
ch := &TelegramChannel{
|
||||||
|
BaseChannel: channels.NewBaseChannel("telegram", nil, messageBus, nil),
|
||||||
|
chatIDs: make(map[string]int64),
|
||||||
|
ctx: context.Background(),
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := &telego.Message{
|
||||||
|
Text: "what do you think?",
|
||||||
|
MessageID: 21,
|
||||||
|
Chat: telego.Chat{
|
||||||
|
ID: 12345,
|
||||||
|
Type: "private",
|
||||||
|
},
|
||||||
|
From: &telego.User{
|
||||||
|
ID: 10,
|
||||||
|
FirstName: "Bob",
|
||||||
|
},
|
||||||
|
ReplyToMessage: &telego.Message{
|
||||||
|
MessageID: 99,
|
||||||
|
Text: "this is the old message",
|
||||||
|
From: &telego.User{
|
||||||
|
Username: "alice",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := ch.handleMessage(context.Background(), msg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
inbound, ok := <-messageBus.InboundChan()
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Equal(t, "99", inbound.Metadata["reply_to_message_id"])
|
||||||
|
assert.Equal(
|
||||||
|
t,
|
||||||
|
"[quoted message from alice]: this is the old message\n\nwhat do you think?",
|
||||||
|
inbound.Content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMessage_ReplyWithoutTextOnlySetsReplyMetadata(t *testing.T) {
|
||||||
|
messageBus := bus.NewMessageBus()
|
||||||
|
ch := &TelegramChannel{
|
||||||
|
BaseChannel: channels.NewBaseChannel("telegram", nil, messageBus, nil),
|
||||||
|
chatIDs: make(map[string]int64),
|
||||||
|
ctx: context.Background(),
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := &telego.Message{
|
||||||
|
Text: "ping",
|
||||||
|
MessageID: 22,
|
||||||
|
Chat: telego.Chat{
|
||||||
|
ID: 12345,
|
||||||
|
Type: "private",
|
||||||
|
},
|
||||||
|
From: &telego.User{
|
||||||
|
ID: 10,
|
||||||
|
FirstName: "Bob",
|
||||||
|
},
|
||||||
|
ReplyToMessage: &telego.Message{
|
||||||
|
MessageID: 100,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := ch.handleMessage(context.Background(), msg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
inbound, ok := <-messageBus.InboundChan()
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Equal(t, "100", inbound.Metadata["reply_to_message_id"])
|
||||||
|
assert.Equal(t, "ping", inbound.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMessage_ReplyCommandKeepsCommandPrefix(t *testing.T) {
|
||||||
|
messageBus := bus.NewMessageBus()
|
||||||
|
ch := &TelegramChannel{
|
||||||
|
BaseChannel: channels.NewBaseChannel("telegram", nil, messageBus, nil),
|
||||||
|
chatIDs: make(map[string]int64),
|
||||||
|
ctx: context.Background(),
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := &telego.Message{
|
||||||
|
Text: "/new",
|
||||||
|
MessageID: 23,
|
||||||
|
Chat: telego.Chat{
|
||||||
|
ID: 12345,
|
||||||
|
Type: "private",
|
||||||
|
},
|
||||||
|
From: &telego.User{
|
||||||
|
ID: 10,
|
||||||
|
FirstName: "Bob",
|
||||||
|
},
|
||||||
|
ReplyToMessage: &telego.Message{
|
||||||
|
MessageID: 101,
|
||||||
|
Text: "old context text",
|
||||||
|
From: &telego.User{
|
||||||
|
Username: "alice",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := ch.handleMessage(context.Background(), msg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
inbound, ok := <-messageBus.InboundChan()
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Equal(t, "101", inbound.Metadata["reply_to_message_id"])
|
||||||
|
assert.Equal(t, "/new", inbound.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleMessage_ReplyBangCommandKeepsCommandPrefix(t *testing.T) {
|
||||||
|
messageBus := bus.NewMessageBus()
|
||||||
|
ch := &TelegramChannel{
|
||||||
|
BaseChannel: channels.NewBaseChannel("telegram", nil, messageBus, nil),
|
||||||
|
chatIDs: make(map[string]int64),
|
||||||
|
ctx: context.Background(),
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := &telego.Message{
|
||||||
|
Text: "!new",
|
||||||
|
MessageID: 24,
|
||||||
|
Chat: telego.Chat{
|
||||||
|
ID: 12345,
|
||||||
|
Type: "private",
|
||||||
|
},
|
||||||
|
From: &telego.User{
|
||||||
|
ID: 10,
|
||||||
|
FirstName: "Bob",
|
||||||
|
},
|
||||||
|
ReplyToMessage: &telego.Message{
|
||||||
|
MessageID: 102,
|
||||||
|
Text: "old context text",
|
||||||
|
From: &telego.User{
|
||||||
|
Username: "alice",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := ch.handleMessage(context.Background(), msg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
inbound, ok := <-messageBus.InboundChan()
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Equal(t, "102", inbound.Metadata["reply_to_message_id"])
|
||||||
|
assert.Equal(t, "!new", inbound.Content)
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue