Skip to content

Commit c4ce54d

Browse files
committed
1 parent 61b0553 commit c4ce54d

File tree

3 files changed

+100
-3
lines changed

3 files changed

+100
-3
lines changed

pushrules/condition.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@
77
package pushrules
88

99
import (
10+
"encoding/json"
1011
"fmt"
1112
"regexp"
1213
"strconv"
1314
"strings"
1415
"unicode"
1516

17+
"github.com/tidwall/gjson"
18+
1619
"maunium.net/go/mautrix/event"
20+
"maunium.net/go/mautrix/id"
1721
"maunium.net/go/mautrix/pushrules/glob"
1822
)
1923

@@ -23,6 +27,12 @@ type Room interface {
2327
GetMemberCount() int
2428
}
2529

30+
// EventfulRoom is an extension of Room to support MSC3664.
31+
type EventfulRoom interface {
32+
Room
33+
GetEvent(id.EventID) *event.Event
34+
}
35+
2636
// PushCondKind is the type of a push condition.
2737
type PushCondKind string
2838

@@ -31,6 +41,11 @@ const (
3141
KindEventMatch PushCondKind = "event_match"
3242
KindContainsDisplayName PushCondKind = "contains_display_name"
3343
KindRoomMemberCount PushCondKind = "room_member_count"
44+
45+
// MSC3664: https://github.com/matrix-org/matrix-spec-proposals/pull/3664
46+
47+
KindRelatedEventMatch PushCondKind = "related_event_match"
48+
KindUnstableRelatedEventMatch PushCondKind = "im.nheko.msc3664.related_event_match"
3449
)
3550

3651
// PushCondition wraps a condition that is required for a specific PushRule to be used.
@@ -44,6 +59,9 @@ type PushCondition struct {
4459
// The condition that needs to be fulfilled for RoomMemberCount-type conditions.
4560
// A decimal integer optionally prefixed by ==, <, >, >= or <=. Prefix "==" is assumed if no prefix found.
4661
MemberCountCondition string `json:"is,omitempty"`
62+
63+
// The relation type for related_event_match from MSC3664
64+
RelType event.RelationType `json:"rel_type,omitempty"`
4765
}
4866

4967
// MemberCountFilterRegex is the regular expression to parse the MemberCountCondition of PushConditions.
@@ -54,6 +72,8 @@ func (cond *PushCondition) Match(room Room, evt *event.Event) bool {
5472
switch cond.Kind {
5573
case KindEventMatch:
5674
return cond.matchValue(room, evt)
75+
case KindRelatedEventMatch, KindUnstableRelatedEventMatch:
76+
return cond.matchRelatedEvent(room, evt)
5777
case KindContainsDisplayName:
5878
return cond.matchDisplayName(room, evt)
5979
case KindRoomMemberCount:
@@ -148,6 +168,47 @@ func (cond *PushCondition) matchValue(room Room, evt *event.Event) bool {
148168
}
149169
}
150170

171+
func (cond *PushCondition) getRelationEventID(relatesTo *event.RelatesTo) id.EventID {
172+
if relatesTo == nil {
173+
return ""
174+
}
175+
switch cond.RelType {
176+
case "":
177+
return relatesTo.EventID
178+
case "m.in_reply_to":
179+
if relatesTo.IsFallingBack || relatesTo.InReplyTo == nil {
180+
return ""
181+
}
182+
return relatesTo.InReplyTo.EventID
183+
default:
184+
if relatesTo.Type != cond.RelType {
185+
return ""
186+
}
187+
return relatesTo.EventID
188+
}
189+
}
190+
191+
func (cond *PushCondition) matchRelatedEvent(room Room, evt *event.Event) bool {
192+
var relatesTo *event.RelatesTo
193+
if relatable, ok := evt.Content.Parsed.(event.Relatable); ok {
194+
relatesTo = relatable.OptionalGetRelatesTo()
195+
} else {
196+
res := gjson.GetBytes(evt.Content.VeryRaw, `m\.relates_to`)
197+
if res.Exists() && res.IsObject() {
198+
_ = json.Unmarshal([]byte(res.Str), &relatesTo)
199+
}
200+
}
201+
if evtID := cond.getRelationEventID(relatesTo); evtID == "" {
202+
return false
203+
} else if eventfulRoom, ok := room.(EventfulRoom); !ok {
204+
return false
205+
} else if evt = eventfulRoom.GetEvent(relatesTo.EventID); evt == nil {
206+
return false
207+
} else {
208+
return cond.matchValue(room, evt)
209+
}
210+
}
211+
151212
func (cond *PushCondition) matchDisplayName(room Room, evt *event.Event) bool {
152213
displayname := room.GetOwnDisplayname()
153214
if len(displayname) == 0 {

pushrules/condition_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/stretchr/testify/assert"
1515

1616
"maunium.net/go/mautrix/event"
17+
"maunium.net/go/mautrix/id"
1718
"maunium.net/go/mautrix/pushrules"
1819
)
1920

@@ -104,12 +105,15 @@ func TestPushCondition_Match_InvalidKind(t *testing.T) {
104105
type FakeRoom struct {
105106
members map[string]*event.MemberEventContent
106107
owner string
108+
109+
events map[id.EventID]*event.Event
107110
}
108111

109112
func newFakeRoom(memberCount int) *FakeRoom {
110113
room := &FakeRoom{
111114
owner: "@tulir:maunium.net",
112115
members: make(map[string]*event.MemberEventContent),
116+
events: make(map[id.EventID]*event.Event),
113117
}
114118

115119
if memberCount >= 1 {
@@ -141,3 +145,7 @@ func (fr *FakeRoom) GetOwnDisplayname() string {
141145
}
142146
return ""
143147
}
148+
149+
func (fr *FakeRoom) GetEvent(evtID id.EventID) *event.Event {
150+
return fr.events[evtID]
151+
}

pushrules/rule_test.go

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func TestPushRule_Match_Conditions_NestedKey_Boolean(t *testing.T) {
6464
Conditions: []*pushrules.PushCondition{cond1},
6565
}
6666

67-
evt := newFakeEvent(event.EventMessage, &event.MemberEventContent{
67+
evt := newFakeEvent(event.StateMember, &event.MemberEventContent{
6868
Membership: "invite",
6969
})
7070
assert.False(t, rule.Match(blankTestRoom, evt))
@@ -86,7 +86,7 @@ func TestPushRule_Match_Conditions_EscapedKey(t *testing.T) {
8686
Conditions: []*pushrules.PushCondition{cond1},
8787
}
8888

89-
evt := newFakeEvent(event.EventMessage, &event.MemberEventContent{
89+
evt := newFakeEvent(event.StateMember, &event.MemberEventContent{
9090
Membership: "invite",
9191
})
9292
assert.False(t, rule.Match(blankTestRoom, evt))
@@ -102,7 +102,7 @@ func TestPushRule_Match_Conditions_EscapedKey_NoNesting(t *testing.T) {
102102
Conditions: []*pushrules.PushCondition{cond1},
103103
}
104104

105-
evt := newFakeEvent(event.EventMessage, &event.MemberEventContent{
105+
evt := newFakeEvent(event.StateMember, &event.MemberEventContent{
106106
Membership: "invite",
107107
})
108108
assert.False(t, rule.Match(blankTestRoom, evt))
@@ -112,6 +112,34 @@ func TestPushRule_Match_Conditions_EscapedKey_NoNesting(t *testing.T) {
112112
assert.False(t, rule.Match(blankTestRoom, evt))
113113
}
114114

115+
func TestPushRule_Match_Conditions_RelatedEvent(t *testing.T) {
116+
cond1 := &pushrules.PushCondition{
117+
Kind: pushrules.KindRelatedEventMatch,
118+
Key: "sender",
119+
Pattern: "@tulir:maunium.net",
120+
}
121+
rule := &pushrules.PushRule{
122+
Type: pushrules.OverrideRule,
123+
Enabled: true,
124+
Conditions: []*pushrules.PushCondition{cond1},
125+
}
126+
127+
evt := newFakeEvent(event.EventReaction, &event.ReactionEventContent{
128+
RelatesTo: event.RelatesTo{
129+
Type: event.RelAnnotation,
130+
EventID: "$meow",
131+
Key: "🐈️",
132+
},
133+
})
134+
roomWithEvent := newFakeRoom(1)
135+
assert.False(t, rule.Match(roomWithEvent, evt))
136+
roomWithEvent.events["$meow"] = newFakeEvent(event.EventMessage, &event.MessageEventContent{
137+
MsgType: event.MsgEmote,
138+
Body: "is testing pushrules",
139+
})
140+
assert.True(t, rule.Match(roomWithEvent, evt))
141+
}
142+
115143
func TestPushRule_Match_Conditions_Disabled(t *testing.T) {
116144
cond1 := newMatchPushCondition("content.msgtype", "m.emote")
117145
cond2 := newMatchPushCondition("content.body", "*pushrules")

0 commit comments

Comments
 (0)