Merge branch 'nico/pushrules' into 'main'
feat: Support evaluating pushrules Closes #339 See merge request famedly/company/frontend/famedlysdk!1146
This commit is contained in:
commit
a3e3ab419d
|
|
@ -43,12 +43,13 @@ export 'src/utils/device_keys_list.dart';
|
|||
export 'src/utils/event_update.dart';
|
||||
export 'src/utils/http_timeout.dart';
|
||||
export 'src/utils/image_pack_extension.dart';
|
||||
export 'src/utils/matrix_default_localizations.dart';
|
||||
export 'src/utils/matrix_file.dart';
|
||||
export 'src/utils/matrix_id_string_extension.dart';
|
||||
export 'src/utils/matrix_default_localizations.dart';
|
||||
export 'src/utils/matrix_localizations.dart';
|
||||
export 'src/utils/native_implementations.dart';
|
||||
export 'src/utils/push_notification.dart';
|
||||
export 'src/utils/pushrule_evaluator.dart';
|
||||
export 'src/utils/receipt.dart';
|
||||
export 'src/utils/sync_update_extension.dart';
|
||||
export 'src/utils/to_device_event.dart';
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import 'package:matrix/src/models/timeline_chunk.dart';
|
|||
import 'package:matrix/src/utils/cached_stream_controller.dart';
|
||||
import 'package:matrix/src/utils/compute_callback.dart';
|
||||
import 'package:matrix/src/utils/multilock.dart';
|
||||
import 'package:matrix/src/utils/pushrule_evaluator.dart';
|
||||
import 'package:matrix/src/utils/run_benchmarked.dart';
|
||||
import 'package:matrix/src/utils/run_in_root.dart';
|
||||
import 'package:matrix/src/utils/sync_update_item_count.dart';
|
||||
|
|
@ -278,6 +279,17 @@ class Client extends MatrixApi {
|
|||
|
||||
Map<String, BasicEvent> get accountData => _accountData;
|
||||
|
||||
/// Evaluate if an event should notify quickly
|
||||
PushruleEvaluator get pushruleEvaluator =>
|
||||
_pushruleEvaluator ?? PushruleEvaluator.fromRuleset(PushRuleSet());
|
||||
PushruleEvaluator? _pushruleEvaluator;
|
||||
|
||||
void _updatePushrules() {
|
||||
final ruleset = PushRuleSet.fromJson(
|
||||
_accountData[EventTypes.PushRules]?.content['global'] ?? {});
|
||||
_pushruleEvaluator = PushruleEvaluator.fromRuleset(ruleset);
|
||||
}
|
||||
|
||||
/// Presences of users by a given matrix ID
|
||||
Map<String, CachedPresence> presences = {};
|
||||
|
||||
|
|
@ -1434,8 +1446,10 @@ class Client extends MatrixApi {
|
|||
_rooms = rooms;
|
||||
_sortRooms();
|
||||
});
|
||||
_accountDataLoading =
|
||||
database.getAccountData().then((data) => _accountData = data);
|
||||
_accountDataLoading = database.getAccountData().then((data) {
|
||||
_accountData = data;
|
||||
_updatePushrules();
|
||||
});
|
||||
presences.clear();
|
||||
if (waitUntilLoadCompletedLoaded) {
|
||||
await userDeviceKeysLoading;
|
||||
|
|
@ -1667,6 +1681,10 @@ class Client extends MatrixApi {
|
|||
);
|
||||
accountData[newAccountData.type] = newAccountData;
|
||||
onAccountData.add(newAccountData);
|
||||
|
||||
if (newAccountData.type == EventTypes.PushRules) {
|
||||
_updatePushrules();
|
||||
}
|
||||
}
|
||||
|
||||
final syncDeviceLists = sync.deviceLists;
|
||||
|
|
@ -2606,7 +2624,7 @@ class Client extends MatrixApi {
|
|||
/// rule of the push rules: https://matrix.org/docs/spec/client_server/r0.6.0#m-rule-master
|
||||
bool get allPushNotificationsMuted {
|
||||
final Map<String, dynamic>? globalPushRules =
|
||||
_accountData['m.push_rules']?.content['global'];
|
||||
_accountData[EventTypes.PushRules]?.content['global'];
|
||||
if (globalPushRules == null) return false;
|
||||
|
||||
if (globalPushRules['override'] is List) {
|
||||
|
|
|
|||
|
|
@ -1764,6 +1764,17 @@ class Room {
|
|||
return ownPowerLevel >= pl;
|
||||
}
|
||||
|
||||
bool canSendNotification(String userid, {String notificationType = 'room'}) {
|
||||
final userLevel = getPowerLevelByUserId(userid);
|
||||
final notificationLevel = getState(EventTypes.RoomPowerLevels)
|
||||
?.content
|
||||
.tryGetMap<String, dynamic>('notifications')
|
||||
?.tryGet<int>(notificationType) ??
|
||||
50;
|
||||
|
||||
return userLevel >= notificationLevel;
|
||||
}
|
||||
|
||||
/// Returns the [PushRuleState] for this room, based on the m.push_rules stored in
|
||||
/// the account_data.
|
||||
PushRuleState get pushRuleState {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,327 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020, 2021 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// Helper for fast evaluation of push conditions on a bunch of events
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
class EvaluatedPushRuleAction {
|
||||
// if this message should be highlighted.
|
||||
bool highlight = false;
|
||||
|
||||
// if this is set, play a sound on a notification. Usually the sound is "default".
|
||||
String? sound;
|
||||
|
||||
// If this event should notify.
|
||||
bool notify = false;
|
||||
|
||||
EvaluatedPushRuleAction();
|
||||
|
||||
EvaluatedPushRuleAction.fromActions(List<dynamic> actions) {
|
||||
for (final action in actions) {
|
||||
if (action == 'notify') {
|
||||
notify = true;
|
||||
} else if (action == 'dont_notify') {
|
||||
notify = false;
|
||||
} else if (action is Map<String, dynamic>) {
|
||||
if (action['set_tweak'] == 'highlight') {
|
||||
highlight = action.tryGet<bool>('value') ?? true;
|
||||
} else if (action['set_tweak'] == 'sound') {
|
||||
sound = action.tryGet<String>('value') ?? 'default';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _PatternCondition {
|
||||
RegExp pattern = RegExp('');
|
||||
// what field to match on, i.e. content.body
|
||||
String field = '';
|
||||
|
||||
_PatternCondition.fromEventMatch(PushCondition condition) {
|
||||
if (condition.kind != 'event_match') {
|
||||
throw 'Logic error: invalid push rule passed to constructor ${condition.kind}';
|
||||
}
|
||||
|
||||
final tempField = condition.key;
|
||||
if (tempField == null) {
|
||||
{
|
||||
throw 'No field to match pattern on!';
|
||||
}
|
||||
}
|
||||
field = tempField;
|
||||
|
||||
var tempPat = condition.pattern;
|
||||
if (tempPat == null) {
|
||||
{
|
||||
throw 'PushCondition is missing pattern';
|
||||
}
|
||||
}
|
||||
tempPat =
|
||||
RegExp.escape(tempPat).replaceAll('\\*', '.*').replaceAll('\\?', '.');
|
||||
|
||||
if (field == 'content.body') {
|
||||
pattern = RegExp('(^|\\W)$tempPat(\$|\\W)', caseSensitive: false);
|
||||
} else {
|
||||
pattern = RegExp('^$tempPat\$', caseSensitive: false);
|
||||
}
|
||||
}
|
||||
|
||||
bool match(Map<String, String> content) {
|
||||
final fieldContent = content[field];
|
||||
if (fieldContent == null) {
|
||||
return false;
|
||||
}
|
||||
return pattern.hasMatch(fieldContent);
|
||||
}
|
||||
}
|
||||
|
||||
enum _CountComparisonOp {
|
||||
eq,
|
||||
lt,
|
||||
le,
|
||||
ge,
|
||||
gt,
|
||||
}
|
||||
|
||||
class _MemberCountCondition {
|
||||
_CountComparisonOp op = _CountComparisonOp.eq;
|
||||
int count = 0;
|
||||
|
||||
_MemberCountCondition.fromEventMatch(PushCondition condition) {
|
||||
if (condition.kind != 'room_member_count') {
|
||||
throw 'Logic error: invalid push rule passed to constructor ${condition.kind}';
|
||||
}
|
||||
|
||||
var is_ = condition.is$;
|
||||
|
||||
if (is_ == null) {
|
||||
throw 'Member condition has no condition set: $is_';
|
||||
}
|
||||
|
||||
if (is_.startsWith('==')) {
|
||||
is_ = is_.replaceFirst('==', '');
|
||||
op = _CountComparisonOp.eq;
|
||||
count = int.parse(is_);
|
||||
} else if (is_.startsWith('>=')) {
|
||||
is_ = is_.replaceFirst('>=', '');
|
||||
op = _CountComparisonOp.ge;
|
||||
count = int.parse(is_);
|
||||
} else if (is_.startsWith('<=')) {
|
||||
is_ = is_.replaceFirst('<=', '');
|
||||
op = _CountComparisonOp.le;
|
||||
count = int.parse(is_);
|
||||
} else if (is_.startsWith('>')) {
|
||||
is_ = is_.replaceFirst('>', '');
|
||||
op = _CountComparisonOp.gt;
|
||||
count = int.parse(is_);
|
||||
} else if (is_.startsWith('<')) {
|
||||
is_ = is_.replaceFirst('<', '');
|
||||
op = _CountComparisonOp.lt;
|
||||
count = int.parse(is_);
|
||||
} else {
|
||||
op = _CountComparisonOp.eq;
|
||||
count = int.parse(is_);
|
||||
}
|
||||
}
|
||||
bool match(int memberCount) {
|
||||
switch (op) {
|
||||
case _CountComparisonOp.ge:
|
||||
return memberCount >= count;
|
||||
case _CountComparisonOp.gt:
|
||||
return memberCount > count;
|
||||
case _CountComparisonOp.le:
|
||||
return memberCount <= count;
|
||||
case _CountComparisonOp.lt:
|
||||
return memberCount < count;
|
||||
case _CountComparisonOp.eq:
|
||||
default:
|
||||
return memberCount == count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _OptimizedRules {
|
||||
List<_PatternCondition> patterns = [];
|
||||
List<_MemberCountCondition> memberCounts = [];
|
||||
List<String> notificationPermissions = [];
|
||||
bool matchDisplayname = false;
|
||||
EvaluatedPushRuleAction actions = EvaluatedPushRuleAction();
|
||||
|
||||
_OptimizedRules.fromRule(PushRule rule) {
|
||||
if (!rule.enabled) return;
|
||||
|
||||
for (final condition in rule.conditions ?? []) {
|
||||
switch (condition.kind) {
|
||||
case 'event_match':
|
||||
patterns.add(_PatternCondition.fromEventMatch(condition));
|
||||
break;
|
||||
case 'contains_display_name':
|
||||
matchDisplayname = true;
|
||||
break;
|
||||
case 'room_member_count':
|
||||
memberCounts.add(_MemberCountCondition.fromEventMatch(condition));
|
||||
break;
|
||||
case 'sender_notification_permission':
|
||||
final key = condition.key;
|
||||
if (key != null) {
|
||||
notificationPermissions.add(key);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
actions = EvaluatedPushRuleAction.fromActions(rule.actions);
|
||||
}
|
||||
|
||||
EvaluatedPushRuleAction? match(Map<String, String> event, String? displayName,
|
||||
int memberCount, Room room) {
|
||||
if (patterns.any((pat) => !pat.match(event))) {
|
||||
return null;
|
||||
}
|
||||
if (memberCounts.any((pat) => !pat.match(memberCount))) {
|
||||
return null;
|
||||
}
|
||||
if (matchDisplayname) {
|
||||
final body = event.tryGet<String>('content.body');
|
||||
if (displayName == null || body == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final regex = RegExp('(^|\\W)${RegExp.escape(displayName)}(\$|\\W)',
|
||||
caseSensitive: false);
|
||||
if (!regex.hasMatch(body)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (notificationPermissions.isNotEmpty) {
|
||||
final sender = event.tryGet<String>('sender');
|
||||
if (sender == null ||
|
||||
notificationPermissions.any((notificationType) =>
|
||||
!room.canSendNotification(sender,
|
||||
notificationType: notificationType))) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
class PushruleEvaluator {
|
||||
final List<_OptimizedRules> _override = [];
|
||||
final Map<String, EvaluatedPushRuleAction> _room_rules = {};
|
||||
final Map<String, EvaluatedPushRuleAction> _sender_rules = {};
|
||||
final List<_OptimizedRules> _content_rules = [];
|
||||
final List<_OptimizedRules> _underride = [];
|
||||
|
||||
PushruleEvaluator.fromRuleset(PushRuleSet ruleset) {
|
||||
for (final o in ruleset.override ?? []) {
|
||||
if (!o.enabled) continue;
|
||||
_override.add(_OptimizedRules.fromRule(o));
|
||||
}
|
||||
for (final u in ruleset.underride ?? []) {
|
||||
if (!u.enabled) continue;
|
||||
_underride.add(_OptimizedRules.fromRule(u));
|
||||
}
|
||||
for (final c in ruleset.content ?? []) {
|
||||
if (!c.enabled) continue;
|
||||
final rule = PushRule(
|
||||
actions: c.actions,
|
||||
conditions: [
|
||||
PushCondition(
|
||||
kind: 'event_match', key: 'content.body', pattern: c.pattern)
|
||||
],
|
||||
ruleId: c.ruleId,
|
||||
default$: c.default$,
|
||||
enabled: c.enabled,
|
||||
);
|
||||
_content_rules.add(_OptimizedRules.fromRule(rule));
|
||||
}
|
||||
for (final r in ruleset.room ?? []) {
|
||||
if (r.enabled) {
|
||||
_room_rules[r.ruleId] = EvaluatedPushRuleAction.fromActions(r.actions);
|
||||
}
|
||||
}
|
||||
for (final r in ruleset.sender ?? []) {
|
||||
if (r.enabled) {
|
||||
_sender_rules[r.ruleId] =
|
||||
EvaluatedPushRuleAction.fromActions(r.actions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, String> _flattenJson(
|
||||
Map<String, dynamic> obj, Map<String, String> flattened, String prefix) {
|
||||
for (final entry in obj.entries) {
|
||||
final key = prefix == '' ? entry.key : '$prefix.${entry.key}';
|
||||
final value = entry.value;
|
||||
if (value is String) {
|
||||
flattened[key] = value;
|
||||
} else if (value is Map<String, dynamic>) {
|
||||
flattened = _flattenJson(value, flattened, key);
|
||||
}
|
||||
}
|
||||
|
||||
return flattened;
|
||||
}
|
||||
|
||||
EvaluatedPushRuleAction match(Event event) {
|
||||
final memberCount = event.room.getParticipants([Membership.join]).length;
|
||||
final displayName = event.room
|
||||
.unsafeGetUserFromMemoryOrFallback(event.room.client.userID!)
|
||||
.displayName;
|
||||
final content = _flattenJson(event.toJson(), {}, '');
|
||||
// ensure roomid is present
|
||||
content['room_id'] = event.room.id;
|
||||
|
||||
for (final o in _override) {
|
||||
final actions = o.match(content, displayName, memberCount, event.room);
|
||||
if (actions != null) {
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
final roomActions = _room_rules[event.room.id];
|
||||
if (roomActions != null) {
|
||||
return roomActions;
|
||||
}
|
||||
|
||||
final senderActions = _sender_rules[event.senderId];
|
||||
if (senderActions != null) {
|
||||
return senderActions;
|
||||
}
|
||||
|
||||
for (final o in _content_rules) {
|
||||
final actions = o.match(content, displayName, memberCount, event.room);
|
||||
if (actions != null) {
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
for (final o in _underride) {
|
||||
final actions = o.match(content, displayName, memberCount, event.room);
|
||||
if (actions != null) {
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
return EvaluatedPushRuleAction();
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ dependencies:
|
|||
image: ^3.1.1
|
||||
js: ^0.6.3
|
||||
markdown: ^4.0.0
|
||||
matrix_api_lite: ^1.1.7
|
||||
matrix_api_lite: ^1.1.8
|
||||
mime: ^1.0.0
|
||||
olm: ^2.0.2
|
||||
random_string: ^2.3.1
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ void main() {
|
|||
final matrixException = MatrixException(
|
||||
Response(
|
||||
'{"error":"HAHA"}',
|
||||
401,
|
||||
420,
|
||||
),
|
||||
);
|
||||
expect(matrixException.error, MatrixError.M_UNKNOWN);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,332 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:olm/olm.dart' as olm;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'package:matrix/encryption.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:matrix/src/models/timeline_chunk.dart';
|
||||
import 'fake_client.dart';
|
||||
import 'fake_matrix_api.dart';
|
||||
|
||||
void main() {
|
||||
/// All Tests related to the Event
|
||||
group('Event', () {
|
||||
Logs().level = Level.error;
|
||||
var olmEnabled = true;
|
||||
|
||||
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
final id = '!4fsdfjisjf:server.abc';
|
||||
final senderID = '@alice:server.abc';
|
||||
final type = 'm.room.message';
|
||||
final msgtype = 'm.text';
|
||||
final body = 'Hello fox';
|
||||
final formatted_body = '<b>Hello</b> fox';
|
||||
|
||||
final contentJson =
|
||||
'{"msgtype":"$msgtype","body":"$body","formatted_body":"$formatted_body","m.relates_to":{"m.in_reply_to":{"event_id":"\$1234:example.com"}}}';
|
||||
|
||||
final jsonObj = <String, dynamic>{
|
||||
'event_id': id,
|
||||
'sender': senderID,
|
||||
'origin_server_ts': timestamp,
|
||||
'type': type,
|
||||
'room_id': '!testroom:example.abc',
|
||||
'status': EventStatus.synced.intValue,
|
||||
'content': json.decode(contentJson),
|
||||
};
|
||||
late Client client;
|
||||
late Room room;
|
||||
|
||||
setUpAll(() async {
|
||||
try {
|
||||
await olm.init();
|
||||
olm.get_library_version();
|
||||
} catch (e) {
|
||||
olmEnabled = false;
|
||||
Logs().w('[LibOlm] Failed to load LibOlm', e);
|
||||
}
|
||||
Logs().i('[LibOlm] Enabled: $olmEnabled');
|
||||
client = await getClient();
|
||||
room = Room(id: '!testroom:example.abc', client: client);
|
||||
});
|
||||
|
||||
test('event_match rule', () async {
|
||||
final event = Event.fromJson(jsonObj, room);
|
||||
|
||||
final override_ruleset = PushRuleSet(override: [
|
||||
PushRule(ruleId: 'my.rule', default$: false, enabled: true, actions: [
|
||||
'notify',
|
||||
{'set_tweak': 'highlight', 'value': true},
|
||||
{'set_tweak': 'sound', 'value': 'goose.wav'},
|
||||
], conditions: [
|
||||
PushCondition(
|
||||
kind: 'event_match', pattern: 'fox', key: 'content.body'),
|
||||
])
|
||||
]);
|
||||
final underride_ruleset = PushRuleSet(underride: [
|
||||
PushRule(ruleId: 'my.rule', default$: false, enabled: true, actions: [
|
||||
'notify',
|
||||
{'set_tweak': 'highlight', 'value': true},
|
||||
{'set_tweak': 'sound', 'value': 'goose.wav'},
|
||||
], conditions: [
|
||||
PushCondition(
|
||||
kind: 'event_match', pattern: 'fox', key: 'content.body'),
|
||||
])
|
||||
]);
|
||||
final content_ruleset = PushRuleSet(content: [
|
||||
PushRule(
|
||||
ruleId: 'my.rule',
|
||||
default$: false,
|
||||
enabled: true,
|
||||
actions: [
|
||||
'notify',
|
||||
{'set_tweak': 'highlight', 'value': true},
|
||||
{'set_tweak': 'sound', 'value': 'goose.wav'},
|
||||
],
|
||||
pattern: 'fox',
|
||||
)
|
||||
]);
|
||||
final room_ruleset = PushRuleSet(room: [
|
||||
PushRule(
|
||||
ruleId: room.id,
|
||||
default$: false,
|
||||
enabled: true,
|
||||
actions: [
|
||||
'notify',
|
||||
{'set_tweak': 'highlight', 'value': true},
|
||||
{'set_tweak': 'sound', 'value': 'goose.wav'},
|
||||
],
|
||||
)
|
||||
]);
|
||||
final sender_ruleset = PushRuleSet(sender: [
|
||||
PushRule(
|
||||
ruleId: senderID,
|
||||
default$: false,
|
||||
enabled: true,
|
||||
actions: [
|
||||
'notify',
|
||||
{'set_tweak': 'highlight', 'value': true},
|
||||
{'set_tweak': 'sound', 'value': 'goose.wav'},
|
||||
],
|
||||
)
|
||||
]);
|
||||
|
||||
void testMatch(PushRuleSet ruleset, Event event) {
|
||||
final evaluator = PushruleEvaluator.fromRuleset(ruleset);
|
||||
final actions = evaluator.match(event);
|
||||
expect(actions.notify, true);
|
||||
expect(actions.highlight, true);
|
||||
expect(actions.sound, 'goose.wav');
|
||||
}
|
||||
|
||||
void testNotMatch(PushRuleSet ruleset, Event event) {
|
||||
final evaluator = PushruleEvaluator.fromRuleset(ruleset);
|
||||
final actions = evaluator.match(event);
|
||||
expect(actions.notify, false);
|
||||
expect(actions.highlight, false);
|
||||
expect(actions.sound, null);
|
||||
}
|
||||
|
||||
testMatch(override_ruleset, event);
|
||||
testMatch(underride_ruleset, event);
|
||||
testMatch(content_ruleset, event);
|
||||
testMatch(room_ruleset, event);
|
||||
testMatch(sender_ruleset, event);
|
||||
|
||||
event.content['body'] = 'FoX';
|
||||
testMatch(override_ruleset, event);
|
||||
testMatch(underride_ruleset, event);
|
||||
testMatch(content_ruleset, event);
|
||||
testMatch(room_ruleset, event);
|
||||
testMatch(sender_ruleset, event);
|
||||
|
||||
event.content['body'] = '@FoX:';
|
||||
testMatch(override_ruleset, event);
|
||||
testMatch(underride_ruleset, event);
|
||||
testMatch(content_ruleset, event);
|
||||
testMatch(room_ruleset, event);
|
||||
testMatch(sender_ruleset, event);
|
||||
|
||||
event.content['body'] = 'äFoXü';
|
||||
testMatch(override_ruleset, event);
|
||||
testMatch(underride_ruleset, event);
|
||||
testMatch(content_ruleset, event);
|
||||
testMatch(room_ruleset, event);
|
||||
testMatch(sender_ruleset, event);
|
||||
|
||||
event.content['body'] = 'äFoXu';
|
||||
testNotMatch(override_ruleset, event);
|
||||
testNotMatch(underride_ruleset, event);
|
||||
testNotMatch(content_ruleset, event);
|
||||
testMatch(room_ruleset, event);
|
||||
testMatch(sender_ruleset, event);
|
||||
|
||||
event.content['body'] = 'aFoXü';
|
||||
testNotMatch(override_ruleset, event);
|
||||
testNotMatch(underride_ruleset, event);
|
||||
testNotMatch(content_ruleset, event);
|
||||
testMatch(room_ruleset, event);
|
||||
testMatch(sender_ruleset, event);
|
||||
|
||||
final override_ruleset2 = PushRuleSet(override: [
|
||||
PushRule(ruleId: 'my.rule', default$: false, enabled: true, actions: [
|
||||
'notify',
|
||||
{'set_tweak': 'highlight', 'value': true},
|
||||
{'set_tweak': 'sound', 'value': 'goose.wav'},
|
||||
], conditions: [
|
||||
PushCondition(kind: 'event_match', pattern: senderID, key: 'sender'),
|
||||
])
|
||||
]);
|
||||
|
||||
testMatch(override_ruleset2, event);
|
||||
event.senderId = '@nope:server.tld';
|
||||
testNotMatch(override_ruleset2, event);
|
||||
event.senderId = '${senderID}a';
|
||||
testNotMatch(override_ruleset2, event);
|
||||
event.senderId = 'a$senderID';
|
||||
testNotMatch(override_ruleset2, event);
|
||||
|
||||
event.senderId = senderID;
|
||||
testMatch(override_ruleset2, event);
|
||||
override_ruleset2.override?[0].enabled = false;
|
||||
testNotMatch(override_ruleset2, event);
|
||||
});
|
||||
|
||||
test('match_display_name rule', () async {
|
||||
final event = Event.fromJson(jsonObj, room);
|
||||
(event.room.states[EventTypes.RoomMember] ??= {})[client.userID!] =
|
||||
Event.fromJson({
|
||||
'type': EventTypes.RoomMember,
|
||||
'sender': senderID,
|
||||
'state_key': 'client.senderID',
|
||||
'content': {'displayname': 'Nico', 'membership': 'join'},
|
||||
'room_id': room.id,
|
||||
'origin_server_ts': 5,
|
||||
}, room);
|
||||
|
||||
final ruleset = PushRuleSet(override: [
|
||||
PushRule(ruleId: 'my.rule', default$: false, enabled: true, actions: [
|
||||
'notify',
|
||||
{'set_tweak': 'highlight', 'value': true},
|
||||
{'set_tweak': 'sound', 'value': 'goose.wav'},
|
||||
], conditions: [
|
||||
PushCondition(kind: 'contains_display_name'),
|
||||
])
|
||||
]);
|
||||
event.content['body'] = 'äNicoü';
|
||||
|
||||
final evaluator = PushruleEvaluator.fromRuleset(ruleset);
|
||||
var actions = evaluator.match(event);
|
||||
expect(actions.notify, true);
|
||||
expect(actions.highlight, true);
|
||||
expect(actions.sound, 'goose.wav');
|
||||
|
||||
event.content['body'] = 'äNicou';
|
||||
actions = evaluator.match(event);
|
||||
expect(actions.notify, false);
|
||||
});
|
||||
|
||||
test('member_count rule', () async {
|
||||
final event = Event.fromJson(jsonObj, room);
|
||||
(event.room.states[EventTypes.RoomMember] ??= {})[client.userID!] =
|
||||
Event.fromJson({
|
||||
'type': EventTypes.RoomMember,
|
||||
'sender': senderID,
|
||||
'state_key': 'client.senderID',
|
||||
'content': {'displayname': 'Nico', 'membership': 'join'},
|
||||
'room_id': room.id,
|
||||
'origin_server_ts': 5,
|
||||
}, room);
|
||||
|
||||
final ruleset = PushRuleSet(override: [
|
||||
PushRule(ruleId: 'my.rule', default$: false, enabled: true, actions: [
|
||||
'notify',
|
||||
{'set_tweak': 'highlight', 'value': true},
|
||||
{'set_tweak': 'sound', 'value': 'goose.wav'},
|
||||
], conditions: [
|
||||
PushCondition(kind: 'room_member_count', is$: '<5'),
|
||||
])
|
||||
]);
|
||||
event.content['body'] = 'äNicoü';
|
||||
|
||||
var evaluator = PushruleEvaluator.fromRuleset(ruleset);
|
||||
expect(evaluator.match(event).notify, true);
|
||||
|
||||
ruleset.override?[0].conditions?[0].is$ = '<=0';
|
||||
evaluator = PushruleEvaluator.fromRuleset(ruleset);
|
||||
expect(evaluator.match(event).notify, false);
|
||||
|
||||
ruleset.override?[0].conditions?[0].is$ = '<=1';
|
||||
evaluator = PushruleEvaluator.fromRuleset(ruleset);
|
||||
expect(evaluator.match(event).notify, true);
|
||||
|
||||
ruleset.override?[0].conditions?[0].is$ = '>=1';
|
||||
evaluator = PushruleEvaluator.fromRuleset(ruleset);
|
||||
expect(evaluator.match(event).notify, true);
|
||||
|
||||
ruleset.override?[0].conditions?[0].is$ = '>1';
|
||||
evaluator = PushruleEvaluator.fromRuleset(ruleset);
|
||||
expect(evaluator.match(event).notify, false);
|
||||
|
||||
ruleset.override?[0].conditions?[0].is$ = '==1';
|
||||
evaluator = PushruleEvaluator.fromRuleset(ruleset);
|
||||
expect(evaluator.match(event).notify, true);
|
||||
|
||||
ruleset.override?[0].conditions?[0].is$ = '1';
|
||||
evaluator = PushruleEvaluator.fromRuleset(ruleset);
|
||||
expect(evaluator.match(event).notify, true);
|
||||
});
|
||||
|
||||
test('notification permissions rule', () async {
|
||||
final event = Event.fromJson(jsonObj, room);
|
||||
(event.room.states[EventTypes.RoomPowerLevels] ??= {})[''] =
|
||||
Event.fromJson({
|
||||
'type': EventTypes.RoomMember,
|
||||
'sender': senderID,
|
||||
'state_key': 'client.senderID',
|
||||
'content': {
|
||||
'notifications': {'broom': 20},
|
||||
'users': {senderID: 20},
|
||||
},
|
||||
'room_id': room.id,
|
||||
'origin_server_ts': 5,
|
||||
}, room);
|
||||
|
||||
final ruleset = PushRuleSet(override: [
|
||||
PushRule(ruleId: 'my.rule', default$: false, enabled: true, actions: [
|
||||
'notify',
|
||||
{'set_tweak': 'highlight', 'value': true},
|
||||
{'set_tweak': 'sound', 'value': 'goose.wav'},
|
||||
], conditions: [
|
||||
PushCondition(kind: 'sender_notification_permission', key: 'broom'),
|
||||
])
|
||||
]);
|
||||
|
||||
final evaluator = PushruleEvaluator.fromRuleset(ruleset);
|
||||
expect(evaluator.match(event).notify, true);
|
||||
|
||||
event.senderId = '@a:b.c';
|
||||
expect(evaluator.match(event).notify, false);
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue