fix: slash commands in group chats trigger all bots instead of targeted one

- In group chats, only process slash commands when the message is
  directed at this bot (via @mention or reply-to-bot), preventing
  all bots from responding to the same command.
- Use raw_text metadata (before quote/forward context prepending)
  for command detection so quoted content like "/fs" doesn't
  accidentally match a command.
- Fix isTelegramBotMentioned text_mention entity check to verify
  the mentioned bot matches the current bot, not just any bot.
This commit is contained in:
Acbox
2026-03-12 20:08:55 +08:00
parent e9059fddda
commit 9b771acaa8
5 changed files with 62 additions and 60 deletions
+31 -56
View File
@@ -208,8 +208,14 @@ func (p *ChannelInboundProcessor) HandleInbound(ctx context.Context, cfg channel
identity := state.Identity
// Intercept slash commands before they reach the LLM.
if p.commandHandler != nil && p.commandHandler.IsCommand(text) {
reply, err := p.commandHandler.Execute(ctx, strings.TrimSpace(identity.BotID), strings.TrimSpace(identity.ChannelIdentityID), text)
// Use raw_text (without prepended quote/forward context) so that
// quoted content like "[Reply to Bot: /fs list]\n hello" doesn't
// accidentally match a command.
// In group chats, only process if the message is directed at this bot
// (via @mention or reply) to avoid all bots responding to the same command.
cmdText := rawTextForCommand(msg, text)
if p.commandHandler != nil && p.commandHandler.IsCommand(cmdText) && isDirectedAtBot(msg) {
reply, err := p.commandHandler.Execute(ctx, strings.TrimSpace(identity.BotID), strings.TrimSpace(identity.ChannelIdentityID), cmdText)
if err != nil {
reply = "Error: " + err.Error()
}
@@ -594,7 +600,29 @@ func shouldTriggerAssistantResponse(msg channel.InboundMessage) bool {
if metadataBool(msg.Metadata, "is_reply_to_bot") {
return true
}
return hasCommandPrefix(msg.Message.PlainText(), msg.Metadata)
return false
}
// isDirectedAtBot reports whether the message is explicitly directed at this bot,
// either because it's a direct conversation, the bot is @mentioned, or it's a reply
// to this bot's message.
func isDirectedAtBot(msg channel.InboundMessage) bool {
if isDirectConversationType(msg.Conversation.Type) {
return true
}
return metadataBool(msg.Metadata, "is_mentioned") || metadataBool(msg.Metadata, "is_reply_to_bot")
}
// rawTextForCommand returns the original user text (without prepended
// quote/forward context) for slash-command detection. Adapters store the
// undecorated text as metadata["raw_text"]; this helper falls back to the
// full decorated text when the key is absent (e.g. direct messages or
// adapters that don't prepend context).
func rawTextForCommand(msg channel.InboundMessage, fallback string) string {
if raw, ok := msg.Metadata["raw_text"].(string); ok && strings.TrimSpace(raw) != "" {
return raw
}
return fallback
}
func isDirectConversationType(conversationType string) bool {
@@ -602,59 +630,6 @@ func isDirectConversationType(conversationType string) bool {
return ct == "" || ct == "p2p" || ct == "private" || ct == "direct"
}
func hasCommandPrefix(text string, metadata map[string]any) bool {
trimmed := strings.TrimSpace(text)
if trimmed == "" {
return false
}
prefixes := []string{"/"}
if metadata != nil {
if raw, ok := metadata["command_prefix"]; ok {
if value := strings.TrimSpace(fmt.Sprint(raw)); value != "" {
prefixes = []string{value}
}
}
if raw, ok := metadata["command_prefixes"]; ok {
if parsed := parseCommandPrefixes(raw); len(parsed) > 0 {
prefixes = parsed
}
}
}
for _, prefix := range prefixes {
if strings.HasPrefix(trimmed, prefix) {
return true
}
}
return false
}
func parseCommandPrefixes(raw any) []string {
if items, ok := raw.([]string); ok {
result := make([]string, 0, len(items))
for _, item := range items {
value := strings.TrimSpace(item)
if value == "" {
continue
}
result = append(result, value)
}
return result
}
items, ok := raw.([]any)
if !ok {
return nil
}
result := make([]string, 0, len(items))
for _, item := range items {
value := strings.TrimSpace(fmt.Sprint(item))
if value == "" {
continue
}
result = append(result, value)
}
return result
}
func metadataBool(metadata map[string]any, key string) bool {
if metadata == nil {
return false