Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement MSC3966: a push rule condition to check if an array contains a value #3180

Merged
merged 4 commits into from
Mar 7, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions spec/unit/pushprocessor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,79 @@ describe("NotificationService", function () {
});
});

describe("Test event property contains", () => {
it.each([
// Simple string matching.
{ value: "bar", eventValue: ["bar"], expected: true },
// Matches are case-sensitive.
{ value: "bar", eventValue: ["BAR"], expected: false },
// Values should not be type-coerced.
{ value: "bar", eventValue: [true], expected: false },
{ value: "bar", eventValue: [1], expected: false },
{ value: "bar", eventValue: [false], expected: false },
// Boolean matching.
{ value: true, eventValue: [true], expected: true },
{ value: false, eventValue: [false], expected: true },
// Types should not be coerced.
{ value: true, eventValue: ["true"], expected: false },
{ value: true, eventValue: [1], expected: false },
{ value: false, eventValue: [null], expected: false },
// Null matching.
{ value: null, eventValue: [null], expected: true },
// Types should not be coerced
{ value: null, eventValue: [false], expected: false },
{ value: null, eventValue: [0], expected: false },
{ value: null, eventValue: [""], expected: false },
{ value: null, eventValue: [undefined], expected: false },
// Non-array or empty values should never be matched.
{ value: "bar", eventValue: "bar", expected: false },
{ value: "bar", eventValue: { bar: true }, expected: false },
{ value: true, eventValue: { true: true }, expected: false },
{ value: true, eventValue: true, expected: false },
{ value: null, eventValue: [], expected: false },
{ value: null, eventValue: {}, expected: false },
{ value: null, eventValue: null, expected: false },
{ value: null, eventValue: undefined, expected: false },
])("test $value against $eventValue", ({ value, eventValue, expected }) => {
matrixClient.pushRules! = {
global: {
override: [
{
actions: [PushRuleActionName.Notify],
conditions: [
{
kind: ConditionKind.EventPropertyContains,
key: "content.foo",
value: value,
},
],
default: true,
enabled: true,
rule_id: ".m.rule.test",
},
],
},
};

testEvent = utils.mkEvent({
type: "m.room.message",
room: testRoomId,
user: "@alfred:localhost",
event: true,
content: {
foo: eventValue,
},
});

const actions = pushProcessor.actionsForEvent(testEvent);
if (expected) {
expect(actions?.notify).toBeTruthy();
} else {
expect(actions?.notify).toBeFalsy();
}
});
});

it.each([
// The properly escaped key works.
{ key: "content.m\\.test.foo", pattern: "bar", expected: true },
Expand Down
7 changes: 7 additions & 0 deletions src/@types/PushRules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export function isDmMemberCountCondition(condition: AnyMemberCountCondition): bo
export enum ConditionKind {
EventMatch = "event_match",
EventPropertyIs = "event_property_is",
EventPropertyContains = "event_property_contains",
ContainsDisplayName = "contains_display_name",
RoomMemberCount = "room_member_count",
SenderNotificationPermission = "sender_notification_permission",
Expand All @@ -88,6 +89,11 @@ export interface IEventPropertyIsCondition extends IPushRuleCondition<ConditionK
value: string | boolean | null | number;
}

export interface IEventPropertyContainsCondition extends IPushRuleCondition<ConditionKind.EventPropertyContains> {
key: string;
value: string | boolean | null | number;
}

export interface IContainsDisplayNameCondition extends IPushRuleCondition<ConditionKind.ContainsDisplayName> {
// no additional fields
}
Expand All @@ -114,6 +120,7 @@ export interface ICallStartedPrefixCondition extends IPushRuleCondition<Conditio
export type PushRuleCondition =
| IEventMatchCondition
| IEventPropertyIsCondition
| IEventPropertyContainsCondition
| IContainsDisplayNameCondition
| IRoomMemberCountCondition
| ISenderNotificationPermissionCondition
Expand Down
21 changes: 21 additions & 0 deletions src/pushprocessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
IContainsDisplayNameCondition,
IEventMatchCondition,
IEventPropertyIsCondition,
IEventPropertyContainsCondition,
IPushRule,
IPushRules,
IRoomMemberCountCondition,
Expand Down Expand Up @@ -340,6 +341,8 @@ export class PushProcessor {
return this.eventFulfillsEventMatchCondition(cond, ev);
case ConditionKind.EventPropertyIs:
return this.eventFulfillsEventPropertyIsCondition(cond, ev);
case ConditionKind.EventPropertyContains:
return this.eventFulfillsEventPropertyContains(cond, ev);
case ConditionKind.ContainsDisplayName:
return this.eventFulfillsDisplayNameCondition(cond, ev);
case ConditionKind.RoomMemberCount:
Expand Down Expand Up @@ -488,6 +491,24 @@ export class PushProcessor {
return cond.value === this.valueForDottedKey(cond.key, ev);
}

/**
* Check whether the given event matches the push rule condition by fetching
* the property from the event and comparing exactly against the condition's
* value.
* @param cond - The push rule condition to check for a match.
* @param ev - The event to check for a match.
*/
private eventFulfillsEventPropertyContains(cond: IEventPropertyContainsCondition, ev: MatrixEvent): boolean {
if (!cond.key || cond.value === undefined) {
return false;
}
const val = this.valueForDottedKey(cond.key, ev);
if (!Array.isArray(val)) {
return false;
}
return val.includes(cond.value);
}

private eventFulfillsCallStartedCondition(
_cond: ICallStartedCondition | ICallStartedPrefixCondition,
ev: MatrixEvent,
Expand Down