1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-08-19 16:42:09 +03:00

Port over push rule eveluator from the angular SDK and make it available on MatrixEvent

This commit is contained in:
David Baker
2015-06-29 15:53:23 +01:00
parent 21150e7587
commit 1be02a3c3c
3 changed files with 227 additions and 0 deletions

View File

@@ -1,3 +1,5 @@
var PushProcessor = require('../pushprocessor');
"use strict";
/**
* This is an internal module. See {@link MatrixEvent} and {@link RoomEvent} for
@@ -142,5 +144,13 @@ module.exports.MatrixEvent.prototype = {
*/
isState: function() {
return this.event.state_key !== undefined;
},
getPushActions(client) {
if (this.pushActions === undefined) {
var pushProcessor = new PushProcessor(client);
this.pushActions = pushProcessor.actionsForEvent(this.event);
}
return this.pushActions;
}
};

216
lib/pushprocessor.js Normal file
View File

@@ -0,0 +1,216 @@
var micromatch = require("micromatch");
module.exports = function(client) {
var escapeRegExp = function(string){
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
};
var matchingRuleFromKindSet = function(ev, kindset, device) {
var rulekinds_in_order = ['override', 'content', 'room', 'sender', 'underride'];
for (var ruleKindIndex = 0; ruleKindIndex < rulekinds_in_order.length; ++ruleKindIndex) {
var kind = rulekinds_in_order[ruleKindIndex];
var ruleset = kindset[kind];
for (var ruleIndex = 0; ruleIndex < ruleset.length; ++ruleIndex) {
var rule = ruleset[ruleIndex];
if (!rule.enabled) { continue; }
var rawrule = templateRuleToRaw(kind, rule, device);
if (!rawrule) { continue; }
if (ruleMatchesEvent(rawrule, ev)) {
rule.kind = kind;
return rule;
}
}
}
return null;
};
var templateRuleToRaw = function(kind, tprule, device) {
var rawrule = {
'rule_id': tprule.rule_id,
'actions': tprule.actions,
'conditions': []
};
switch (kind) {
case 'underride':
case 'override':
rawrule.conditions = tprule.conditions;
break;
case 'room':
if (!tprule.rule_id) { return null; }
rawrule.conditions.push({
'kind': 'event_match',
'key': 'room_id',
'pattern': tprule.rule_id
});
break;
case 'sender':
if (!tprule.rule_id) { return null; }
rawrule.conditions.push({
'kind': 'event_match',
'key': 'user_id',
'pattern': tprule.rule_id
});
break;
case 'content':
if (!tprule.pattern) { return null; }
rawrule.conditions.push({
'kind': 'event_match',
'key': 'content.body',
'pattern': tprule.pattern
});
break;
}
if (device) {
rawrule.conditions.push({
'kind': 'device',
'profile_tag': device,
});
}
return rawrule;
};
var ruleMatchesEvent = function(rule, ev) {
var ret = true;
for (var i = 0; i < rule.conditions.length; ++i) {
var cond = rule.conditions[i];
ret &= eventFulfillsCondition(cond, ev);
}
//console.log("Rule "+rule.rule_id+(ret ? " matches" : " doesn't match"));
return ret;
};
var eventFulfillsCondition = function(cond, ev) {
var condition_functions = {
"event_match": eventFulfillsEventMatchCondition,
"device": eventFulfillsDeviceCondition,
"contains_display_name": eventFulfillsDisplayNameCondition,
"room_member_count": eventFulfillsRoomMemberCountCondition
};
if (condition_functions[cond.kind]) { return condition_functions[cond.kind](cond, ev); }
return true;
};
var eventFulfillsRoomMemberCountCondition = function(cond, ev) {
if (!cond.is) { return false; }
var room = client.getRoom(ev.room_id);
if (!room || !room.currentState || !room.currentState.members) { return false; }
var memberCount = Object.keys(room.currentState.members).length;
var m = cond.is.match(/^([=<>]*)([0-9]*)$/);
if (!m) { return false; }
var ineq = m[1];
var rhs = parseInt(m[2]);
if (isNaN(rhs)) { return false; }
switch (ineq) {
case '':
case '==':
return memberCount == rhs;
case '<':
return memberCount < rhs;
case '>':
return memberCount > rhs;
case '<=':
return memberCount <= rhs;
case '>=':
return memberCount >= rhs;
default:
return false;
}
};
var eventFulfillsDisplayNameCondition = function(cond, ev) {
if (!ev.content || ! ev.content.body || typeof ev.content.body != 'string') { return false; }
var room = client.getRoom(ev.room_id);
var displayName = room.currentState.getMember(client.credentials.userId).name;
var pat = new RegExp("\\b"+escapeRegExp(displayName)+"\\b", 'i');
return ev.content.body.search(pat) > -1;
};
var eventFulfillsDeviceCondition = function(cond, ev) {
return false; // XXX: Allow a profile tag to be set for the web client instance
};
var eventFulfillsEventMatchCondition = function(cond, ev) {
var val = valueForDottedKey(cond.key, ev);
if (!val || typeof val != 'string') { return false; }
// Supportting ! in globs would mean figuring out when we don't want to use things as a regex, like room IDs
var pat = cond.pattern.replace("!", "\\!");
if (cond.key == 'content.body') {
return micromatch.contains(val, pat);
} else {
return micromatch.isMatch(val, pat);
}
};
var valueForDottedKey = function(key, ev) {
var parts = key.split('.');
var val = ev;
while (parts.length > 0) {
var thispart = parts.shift();
if (!val[thispart]) { return null; }
val = val[thispart];
}
return val;
};
var matchingRuleForEventWithRulesets = function(ev, rulesets) {
if (!rulesets) { return null; }
if (ev.user_id == client.credentials.userId) { return null; }
var allDevNames = Object.keys(rulesets.device);
for (var i = 0; i < allDevNames.length; ++i) {
var devname = allDevNames[i];
var devrules = rulesets.device[devname];
var matchingRule = matchingRuleFromKindSet(devrules, devname);
if (matchingRule) { return matchingRule; }
}
return matchingRuleFromKindSet(ev, rulesets.global);
};
var actionListToActionsObject = function(actionlist) {
var actionobj = { 'notify': false, 'tweaks': {} };
for (var i = 0; i < actionlist.length; ++i) {
var action = actionlist[i];
if (action === 'notify') {
actionobj.notify = true;
} else if (typeof action === 'object') {
if (action.value === undefined) { action.value = true; }
actionobj.tweaks[action.set_tweak] = action.value;
}
}
return actionobj;
};
var pushActionsForEventAndRulesets = function(ev, rulesets) {
var rule = matchingRuleForEventWithRulesets(ev, rulesets);
if (!rule) { return {}; }
var actionObj = actionListToActionsObject(rule.actions);
// Some actions are implicit in some situations: we add those here
if (actionObj.tweaks.highlight === undefined) {
// if it isn't specified, highlight if it's a content
// rule but otherwise not
actionObj.tweaks.highlight = (rule.kind == 'content');
}
return actionObj;
};
this.actionsForEvent = function(ev) {
return pushActionsForEventAndRulesets(ev, client.pushRules);
};
}

View File

@@ -22,6 +22,7 @@
"dependencies": {
"browser-request": "^0.3.3",
"browserify": "^10.2.3",
"micromatch": "^2.1.6",
"q": "^1.4.1",
"request": "^2.53.0"
},