diff --git a/pkg/channels/matrix/matrix.go b/pkg/channels/matrix/matrix.go
index 0d4c62ac5..87dd37aa8 100644
--- a/pkg/channels/matrix/matrix.go
+++ b/pkg/channels/matrix/matrix.go
@@ -3,7 +3,9 @@ package matrix
import (
"context"
"fmt"
+ "html"
"mime"
+ "net/url"
"os"
"path/filepath"
"regexp"
@@ -29,6 +31,8 @@ const (
roomKindCacheTTL = 5 * time.Minute
)
+var matrixMentionHrefRegexp = regexp.MustCompile(`(?i)]+href=["']([^"']+)["']`)
+
type roomKindCacheEntry struct {
isGroup bool
expiresAt time.Time
@@ -469,6 +473,12 @@ func (c *MatrixChannel) handleMessageEvent(ctx context.Context, evt *event.Event
}
respond, cleaned := c.ShouldRespondInGroup(isMentioned, content)
if !respond {
+ logger.DebugCF("matrix", "Ignoring group message by trigger rules", map[string]any{
+ "room_id": roomID,
+ "is_mentioned": isMentioned,
+ "mention_only": c.config.GroupTrigger.MentionOnly,
+ "prefixes": c.config.GroupTrigger.Prefixes,
+ })
return
}
content = cleaned
@@ -807,7 +817,10 @@ func (c *MatrixChannel) isBotMentioned(msgEvt *event.MessageEventContent) bool {
}
userID := c.client.UserID.String()
- if userID != "" && (strings.Contains(msgEvt.Body, userID) || strings.Contains(msgEvt.FormattedBody, userID)) {
+ if userID != "" && strings.Contains(msgEvt.Body, userID) {
+ return true
+ }
+ if mentionsUserInFormattedBody(msgEvt.FormattedBody, c.client.UserID) {
return true
}
@@ -820,6 +833,63 @@ func (c *MatrixChannel) isBotMentioned(msgEvt *event.MessageEventContent) bool {
return re.MatchString(msgEvt.Body) || re.MatchString(msgEvt.FormattedBody)
}
+func mentionsUserInFormattedBody(formattedBody string, userID id.UserID) bool {
+ target := strings.ToLower(strings.TrimSpace(userID.String()))
+ if target == "" {
+ return false
+ }
+
+ formattedBody = strings.TrimSpace(formattedBody)
+ if formattedBody == "" {
+ return false
+ }
+
+ if strings.Contains(strings.ToLower(formattedBody), target) {
+ return true
+ }
+
+ matches := matrixMentionHrefRegexp.FindAllStringSubmatch(formattedBody, -1)
+ for _, match := range matches {
+ if len(match) < 2 {
+ continue
+ }
+ decoded := decodeMatrixMentionHref(match[1])
+ if strings.Contains(strings.ToLower(decoded), target) {
+ return true
+ }
+
+ u, err := url.Parse(decoded)
+ if err != nil {
+ continue
+ }
+
+ if strings.Contains(strings.ToLower(u.Path), target) || strings.Contains(strings.ToLower(u.Fragment), target) {
+ return true
+ }
+ if strings.Contains(strings.ToLower(decodeMatrixMentionHref(u.Fragment)), target) {
+ return true
+ }
+ }
+
+ return false
+}
+
+func decodeMatrixMentionHref(v string) string {
+ decoded := html.UnescapeString(strings.TrimSpace(v))
+ if decoded == "" {
+ return ""
+ }
+
+ for i := 0; i < 2; i++ {
+ next, err := url.QueryUnescape(decoded)
+ if err != nil || next == decoded {
+ break
+ }
+ decoded = next
+ }
+ return decoded
+}
+
func (c *MatrixChannel) typingLoop(ctx context.Context, roomID id.RoomID, session *typingSession) {
sendTyping := func() {
_, err := c.client.UserTyping(ctx, roomID, true, typingServerTTL)
diff --git a/pkg/channels/matrix/matrix_test.go b/pkg/channels/matrix/matrix_test.go
index 68e4bcb70..af31c671d 100644
--- a/pkg/channels/matrix/matrix_test.go
+++ b/pkg/channels/matrix/matrix_test.go
@@ -91,6 +91,22 @@ func TestIsBotMentioned(t *testing.T) {
},
want: false,
},
+ {
+ name: "formatted mention href matrix.to plain",
+ msg: event.MessageEventContent{
+ Body: "hello bot",
+ FormattedBody: `PicoClaw hello`,
+ },
+ want: true,
+ },
+ {
+ name: "formatted mention href matrix.to encoded",
+ msg: event.MessageEventContent{
+ Body: "hello bot",
+ FormattedBody: `PicoClaw hello`,
+ },
+ want: true,
+ },
}
for _, tc := range cases {