Skip to content

Commit acdc278

Browse files
committed
Support MSC3966 to match values in an array in push rule conditions.
1 parent db18338 commit acdc278

File tree

3 files changed

+102
-0
lines changed

3 files changed

+102
-0
lines changed

spec/unit/pushprocessor.spec.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,79 @@ describe("NotificationService", function () {
578578
});
579579
});
580580

581+
describe("Test event property contains", () => {
582+
it.each([
583+
// Simple string matching.
584+
{ value: "bar", eventValue: ["bar"], expected: true },
585+
// Matches are case-sensitive.
586+
{ value: "bar", eventValue: ["BAR"], expected: false },
587+
// Values should not be type-coerced.
588+
{ value: "bar", eventValue: [true], expected: false },
589+
{ value: "bar", eventValue: [1], expected: false },
590+
{ value: "bar", eventValue: [false], expected: false },
591+
// Boolean matching.
592+
{ value: true, eventValue: [true], expected: true },
593+
{ value: false, eventValue: [false], expected: true },
594+
// Types should not be coerced.
595+
{ value: true, eventValue: ["true"], expected: false },
596+
{ value: true, eventValue: [1], expected: false },
597+
{ value: false, eventValue: [null], expected: false },
598+
// Null matching.
599+
{ value: null, eventValue: [null], expected: true },
600+
// Types should not be coerced
601+
{ value: null, eventValue: [false], expected: false },
602+
{ value: null, eventValue: [0], expected: false },
603+
{ value: null, eventValue: [""], expected: false },
604+
{ value: null, eventValue: [undefined], expected: false },
605+
// Non-array or empty values should never be matched.
606+
{ value: "bar", eventValue: "bar", expected: false },
607+
{ value: "bar", eventValue: { bar: true }, expected: false },
608+
{ value: true, eventValue: { true: true }, expected: false },
609+
{ value: true, eventValue: true, expected: false },
610+
{ value: null, eventValue: [], expected: false },
611+
{ value: null, eventValue: {}, expected: false },
612+
{ value: null, eventValue: null, expected: false },
613+
{ value: null, eventValue: undefined, expected: false },
614+
])("test $value against $eventValue", ({ value, eventValue, expected }) => {
615+
matrixClient.pushRules! = {
616+
global: {
617+
override: [
618+
{
619+
actions: [PushRuleActionName.Notify],
620+
conditions: [
621+
{
622+
kind: ConditionKind.EventPropertyContainsPrefix,
623+
key: "content.foo",
624+
value: value,
625+
},
626+
],
627+
default: true,
628+
enabled: true,
629+
rule_id: ".m.rule.test",
630+
},
631+
],
632+
},
633+
};
634+
635+
testEvent = utils.mkEvent({
636+
type: "m.room.message",
637+
room: testRoomId,
638+
user: "@alfred:localhost",
639+
event: true,
640+
content: {
641+
foo: eventValue,
642+
},
643+
});
644+
645+
const actions = pushProcessor.actionsForEvent(testEvent);
646+
if (expected) {
647+
expect(actions?.notify).toBeTruthy();
648+
} else {
649+
expect(actions?.notify).toBeFalsy();
650+
}
651+
});
652+
});
653+
581654
it.each([
582655
// The properly escaped key works.
583656
{ key: "content.m\\.test.foo", pattern: "bar", expected: true },

src/@types/PushRules.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export function isDmMemberCountCondition(condition: AnyMemberCountCondition): bo
6363
export enum ConditionKind {
6464
EventMatch = "event_match",
6565
ExactEventMatchPrefix = "com.beeper.msc3758.exact_event_match",
66+
EventPropertyContainsPrefix = "org.matrix.msc3966.exact_event_property_contains",
6667
ContainsDisplayName = "contains_display_name",
6768
RoomMemberCount = "room_member_count",
6869
SenderNotificationPermission = "sender_notification_permission",
@@ -88,6 +89,12 @@ export interface IExactEventMatchPrefixCondition extends IPushRuleCondition<Cond
8889
value: string | boolean | null | number;
8990
}
9091

92+
export interface IEventPropertyContainsPrefixCondition
93+
extends IPushRuleCondition<ConditionKind.EventPropertyContainsPrefix> {
94+
key: string;
95+
value: string | boolean | null | number;
96+
}
97+
9198
export interface IContainsDisplayNameCondition extends IPushRuleCondition<ConditionKind.ContainsDisplayName> {
9299
// no additional fields
93100
}
@@ -114,6 +121,7 @@ export interface ICallStartedPrefixCondition extends IPushRuleCondition<Conditio
114121
export type PushRuleCondition =
115122
| IEventMatchCondition
116123
| IExactEventMatchPrefixCondition
124+
| IEventPropertyContainsPrefixCondition
117125
| IContainsDisplayNameCondition
118126
| IRoomMemberCountCondition
119127
| ISenderNotificationPermissionCondition

src/pushprocessor.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
ICallStartedPrefixCondition,
2626
IContainsDisplayNameCondition,
2727
IEventMatchCondition,
28+
IEventPropertyContainsPrefixCondition,
2829
IExactEventMatchPrefixCondition,
2930
IPushRule,
3031
IPushRules,
@@ -340,6 +341,8 @@ export class PushProcessor {
340341
return this.eventFulfillsEventMatchCondition(cond, ev);
341342
case ConditionKind.ExactEventMatchPrefix:
342343
return this.eventFulfillsExactEventMatchCondition(cond, ev);
344+
case ConditionKind.EventPropertyContainsPrefix:
345+
return this.eventFulfillsEventPropertyContains(cond, ev);
343346
case ConditionKind.ContainsDisplayName:
344347
return this.eventFulfillsDisplayNameCondition(cond, ev);
345348
case ConditionKind.RoomMemberCount:
@@ -488,6 +491,24 @@ export class PushProcessor {
488491
return cond.value === this.valueForDottedKey(cond.key, ev);
489492
}
490493

494+
/**
495+
* Check whether the given event matches the push rule condition by fetching
496+
* the property from the event and comparing exactly against the condition's
497+
* value.
498+
* @param cond - The push rule condition to check for a match.
499+
* @param ev - The event to check for a match.
500+
*/
501+
private eventFulfillsEventPropertyContains(cond: IEventPropertyContainsPrefixCondition, ev: MatrixEvent): boolean {
502+
if (!cond.key || cond.value === undefined) {
503+
return false;
504+
}
505+
const val = this.valueForDottedKey(cond.key, ev);
506+
if (!Array.isArray(val)) {
507+
return false;
508+
}
509+
return val.includes(cond.value);
510+
}
511+
491512
private eventFulfillsCallStartedCondition(
492513
_cond: ICallStartedCondition | ICallStartedPrefixCondition,
493514
ev: MatrixEvent,

0 commit comments

Comments
 (0)