fix(telegram): allow group slash commands through mention filter
This commit is contained in:
parent
8d02e92e31
commit
15a1e7afdf
2 changed files with 215 additions and 28 deletions
|
|
@ -659,34 +659,34 @@ func escapeHTML(text string) string {
|
|||
|
||||
// isBotMentioned checks if the bot is mentioned in the message via entities.
|
||||
func (c *TelegramChannel) isBotMentioned(message *telego.Message) bool {
|
||||
botUsername := c.bot.Username()
|
||||
if botUsername == "" {
|
||||
text, entities := telegramEntityTextAndList(message)
|
||||
if text == "" || len(entities) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
entities := message.Entities
|
||||
if entities == nil {
|
||||
entities = message.CaptionEntities
|
||||
}
|
||||
|
||||
for _, entity := range entities {
|
||||
if entity.Type == "mention" {
|
||||
// Extract the mention text from the message
|
||||
text := message.Text
|
||||
if text == "" {
|
||||
text = message.Caption
|
||||
botUsername := ""
|
||||
if c.bot != nil {
|
||||
botUsername = c.bot.Username()
|
||||
}
|
||||
runes := []rune(text)
|
||||
end := entity.Offset + entity.Length
|
||||
if end <= len(runes) {
|
||||
mention := string(runes[entity.Offset:end])
|
||||
if strings.EqualFold(mention, "@"+botUsername) {
|
||||
|
||||
for _, entity := range entities {
|
||||
entityText, ok := telegramEntityText(runes, entity)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
switch entity.Type {
|
||||
case telego.EntityTypeMention:
|
||||
if botUsername != "" && strings.EqualFold(entityText, "@"+botUsername) {
|
||||
return true
|
||||
}
|
||||
case telego.EntityTypeTextMention:
|
||||
if botUsername != "" && entity.User != nil && strings.EqualFold(entity.User.Username, botUsername) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if entity.Type == "text_mention" && entity.User != nil {
|
||||
if entity.User.Username == botUsername {
|
||||
case telego.EntityTypeBotCommand:
|
||||
if isBotCommandEntityForThisBot(entityText, botUsername) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -694,6 +694,46 @@ func (c *TelegramChannel) isBotMentioned(message *telego.Message) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func telegramEntityTextAndList(message *telego.Message) (string, []telego.MessageEntity) {
|
||||
if message.Text != "" {
|
||||
return message.Text, message.Entities
|
||||
}
|
||||
return message.Caption, message.CaptionEntities
|
||||
}
|
||||
|
||||
func telegramEntityText(runes []rune, entity telego.MessageEntity) (string, bool) {
|
||||
if entity.Offset < 0 || entity.Length <= 0 {
|
||||
return "", false
|
||||
}
|
||||
end := entity.Offset + entity.Length
|
||||
if entity.Offset >= len(runes) || end > len(runes) {
|
||||
return "", false
|
||||
}
|
||||
return string(runes[entity.Offset:end]), true
|
||||
}
|
||||
|
||||
func isBotCommandEntityForThisBot(entityText, botUsername string) bool {
|
||||
if !strings.HasPrefix(entityText, "/") {
|
||||
return false
|
||||
}
|
||||
command := strings.TrimPrefix(entityText, "/")
|
||||
if command == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
at := strings.IndexRune(command, '@')
|
||||
if at == -1 {
|
||||
// A bare /command delivered to this bot is intended for this bot.
|
||||
return true
|
||||
}
|
||||
|
||||
mentionUsername := command[at+1:]
|
||||
if mentionUsername == "" || botUsername == "" {
|
||||
return false
|
||||
}
|
||||
return strings.EqualFold(mentionUsername, botUsername)
|
||||
}
|
||||
|
||||
// stripBotMention removes the @bot mention from the content.
|
||||
func (c *TelegramChannel) stripBotMention(content string) string {
|
||||
botUsername := c.bot.Username()
|
||||
|
|
|
|||
147
pkg/channels/telegram/telegram_group_command_filter_test.go
Normal file
147
pkg/channels/telegram/telegram_group_command_filter_test.go
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
package telegram
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mymmrac/telego"
|
||||
ta "github.com/mymmrac/telego/telegoapi"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/bus"
|
||||
"github.com/sipeed/picoclaw/pkg/channels"
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
)
|
||||
|
||||
type getMeCaller struct {
|
||||
username string
|
||||
}
|
||||
|
||||
func (c getMeCaller) Call(_ context.Context, url string, _ *ta.RequestData) (*ta.Response, error) {
|
||||
if strings.HasSuffix(url, "/getMe") {
|
||||
result := fmt.Sprintf(`{"id":1,"is_bot":true,"first_name":"bot","username":%q}`, c.username)
|
||||
return &ta.Response{Ok: true, Result: []byte(result)}, nil
|
||||
}
|
||||
return &ta.Response{Ok: true, Result: []byte("true")}, nil
|
||||
}
|
||||
|
||||
func newTestTelegramBot(t *testing.T, username string) *telego.Bot {
|
||||
t.Helper()
|
||||
|
||||
token := "123456:" + strings.Repeat("a", 35)
|
||||
bot, err := telego.NewBot(token,
|
||||
telego.WithAPICaller(getMeCaller{username: username}),
|
||||
telego.WithDiscardLogger(),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("NewBot error: %v", err)
|
||||
}
|
||||
return bot
|
||||
}
|
||||
|
||||
func newGroupMentionOnlyChannel(t *testing.T, botUsername string) (*TelegramChannel, *bus.MessageBus) {
|
||||
t.Helper()
|
||||
|
||||
messageBus := bus.NewMessageBus()
|
||||
ch := &TelegramChannel{
|
||||
BaseChannel: channels.NewBaseChannel("telegram", nil, messageBus, nil,
|
||||
channels.WithGroupTrigger(config.GroupTriggerConfig{MentionOnly: true}),
|
||||
),
|
||||
bot: newTestTelegramBot(t, botUsername),
|
||||
chatIDs: make(map[string]int64),
|
||||
ctx: context.Background(),
|
||||
}
|
||||
return ch, messageBus
|
||||
}
|
||||
|
||||
func TestHandleMessage_GroupMentionOnly_BotCommandEntity(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
text string
|
||||
wantForwarded bool
|
||||
wantContent string
|
||||
}{
|
||||
{
|
||||
name: "command with bot username",
|
||||
text: "/new@testbot",
|
||||
wantForwarded: true,
|
||||
wantContent: "/new",
|
||||
},
|
||||
{
|
||||
name: "bare command",
|
||||
text: "/new",
|
||||
wantForwarded: true,
|
||||
wantContent: "/new",
|
||||
},
|
||||
{
|
||||
name: "command for another bot",
|
||||
text: "/new@otherbot",
|
||||
wantForwarded: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ch, messageBus := newGroupMentionOnlyChannel(t, "testbot")
|
||||
|
||||
msg := &telego.Message{
|
||||
Text: tc.text,
|
||||
Entities: []telego.MessageEntity{{
|
||||
Type: telego.EntityTypeBotCommand,
|
||||
Offset: 0,
|
||||
Length: len([]rune(tc.text)),
|
||||
}},
|
||||
MessageID: 42,
|
||||
Chat: telego.Chat{
|
||||
ID: 123,
|
||||
Type: "group",
|
||||
},
|
||||
From: &telego.User{
|
||||
ID: 7,
|
||||
FirstName: "Alice",
|
||||
},
|
||||
}
|
||||
|
||||
if err := ch.handleMessage(context.Background(), msg); err != nil {
|
||||
t.Fatalf("handleMessage error: %v", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 150*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
inbound, ok := messageBus.ConsumeInbound(ctx)
|
||||
if tc.wantForwarded {
|
||||
if !ok {
|
||||
t.Fatal("expected inbound message to be forwarded")
|
||||
}
|
||||
if inbound.Content != tc.wantContent {
|
||||
t.Fatalf("content=%q want=%q", inbound.Content, tc.wantContent)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if ok {
|
||||
t.Fatalf("expected message to be filtered, got content=%q", inbound.Content)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsBotMentioned_MentionEntityUnaffected(t *testing.T) {
|
||||
ch, _ := newGroupMentionOnlyChannel(t, "testbot")
|
||||
|
||||
msg := &telego.Message{
|
||||
Text: "@testbot hello",
|
||||
Entities: []telego.MessageEntity{{
|
||||
Type: telego.EntityTypeMention,
|
||||
Offset: 0,
|
||||
Length: len("@testbot"),
|
||||
}},
|
||||
}
|
||||
|
||||
if !ch.isBotMentioned(msg) {
|
||||
t.Fatal("expected mention entity to be treated as bot mention")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue