"use strict"; const matchValue = require("match-value"); const defaultValue = require("default-value"); const syncpipe = require("syncpipe"); const splitFilter = require("split-filter"); const flatten = require("flatten"); const unreachable = require("@joepie91/unreachable")("@modular-matrix/client"); // FIXME function parsePredicate(predicate) { let match = /^(>|>=|<|<=|==)?([0-9]+)$/.exec(predicate); if (match != null) { return { operator: defaultValue(match[1], "=="), value: match[2] }; } else { unreachable("Failed to parse predicate"); } } function mapCondition(condition) { if (condition.kind === "event_match") { return { type: "matchEventField", field: condition.key, pattern: condition.pattern }; } else if (condition.kind === "contains_display_name") { return { type: "matchDisplayName" }; } else if (condition.kind === "room_member_count") { let { operator, value } = parsePredicate(condition.is); return { type: "matchRoomMemberCount", operator: operator, value: value }; } else if (condition.kind === "sender_notification_permission") { return { type: "requireEventPowerLevel", notificationType: condition.key }; } else { // Spec: "Unrecognised conditions MUST NOT match any events, effectively making the push rule disabled." // FIXME: Log warning? return { type: "neverMatch" }; } } function mapAction(action) { if (action === "notify") { return { type: "notification" }; } else if (action === "dont_notify") { return { type: "preventNotification" }; } else if (action === "coalesce") { return { type: "digestNotification" }; } else if (action.set_tweak != null) { let key = action.set_tweak; return { type: "setTweak", key: key, value: matchValue(key, { sound: () => (action.value === "default") ? true : action.value, highlight: () => defaultValue(action.value, true), _: () => action.value }) }; } else { unreachable(`Unrecognized action type`); } } function mapRule(rule) { let mappedActions = rule.actions.map(mapAction); let [ tweaks, actualActions ] = splitFilter(mappedActions, (action) => action.type === "setTweak"); return { id: rule.rule_id, isDefault: rule.default, isEnabled: rule.enabled, actions: actualActions, // FIXME: Clearly document that these 'options' are called 'tweaks' on the push gateway side! notificationOptions: Object.fromEntries(tweaks.map(({ key, value }) => [ key, value ])) }; } function mapRuleset(rules, key) { if (rules == null) { return []; } else if (key === "content") { return rules.map((rule) => { return { ... mapRule(rule), conditions: [{ type: "matchContent", pattern: rule.pattern }] }; }); } else if (key === "room") { return rules.map((rule) => { return { ... mapRule(rule), conditions: [{ type: "matchRoom", roomID: rule.rule_id }] }; }); } else if (key === "sender") { return rules.map((rule) => { return { ... mapRule(rule), conditions: [{ type: "matchSender", userID: rule.rule_id }] }; }); } else if (key === "override" || key === "underride") { return rules.map((rule) => { return { ... mapRule(rule), conditions: rule.conditions.map(mapCondition) }; }); } else { throw unreachable("Unrecognized ruleset key"); } } function mapRulesets(rulesets) { if (rulesets != null) { return syncpipe([ "override", "content", "room", "sender", "underride" ], [ (_) => _.map((key) => mapRuleset(rulesets[key], key)), (_) => _.filter((ruleset) => ruleset.length > 0), (_) => flatten(_) ]); } else { return []; } } module.exports = function mapPushRulesEvent(event, _context) { return { type: "pushRulesChanged", rulesets: mapRulesets(event.content.global) }; };