package pushrules import ( "encoding/json" "strings" ) // The IDs of the server-default rules, from the spec's "Predefined Rules". const ( RuleMaster = ".m.rule.master" RuleSuppressNotices = ".m.rule.suppress_notices" RuleInviteForMe = ".m.rule.invite_for_me" RuleMemberEvent = ".m.rule.member_event" RuleIsUserMention = ".m.rule.is_user_mention" RuleContainsDisplayName = ".m.rule.contains_display_name" RuleIsRoomMention = ".m.rule.is_room_mention" RuleRoomNotif = ".m.rule.roomnotif" RuleTombstone = ".m.rule.tombstone" RuleReaction = ".m.rule.reaction" RuleServerACL = ".m.rule.server_acl" RuleSuppressEdits = ".m.rule.suppress_edits" RuleContainsUserName = ".m.rule.contains_user_name" RuleCall = ".m.rule.call" RuleEncryptedRoomOneToOne = ".m.rule.encrypted_room_one_to_one" RuleRoomOneToOne = ".m.rule.room_one_to_one" RuleMessage = ".m.rule.message" RuleEncrypted = ".m.rule.encrypted" ) // legacyMentionRules are the default rules that guess at a mention from // the body. An event carrying m.mentions says who it mentions outright, // and the spec has these skipped for it: otherwise a reply quoting // somebody's name would ping them although its sender chose not to. var legacyMentionRules = map[string]bool{ RuleContainsDisplayName: true, RuleContainsUserName: true, RuleRoomNotif: true, } // The actions the defaults use, spelled once. var ( actNotify = json.RawMessage(`"notify"`) actSoundDefault = json.RawMessage(`{"set_tweak":"sound","value":"default"}`) actSoundRing = json.RawMessage(`{"set_tweak":"sound","value":"ring"}`) actHighlight = json.RawMessage(`{"set_tweak":"highlight"}`) ) func match(key, pattern string) Condition { return Condition{Kind: CondEventMatch, Key: key, Pattern: &pattern} } func propertyIs(key string, value json.RawMessage) Condition { return Condition{Kind: CondEventPropertyIs, Key: key, Value: value} } func actions(a ...json.RawMessage) []json.RawMessage { return a } func override(id string, enabled bool, acts []json.RawMessage, conds ...Condition) Rule { if conds == nil { conds = []Condition{} } return Rule{RuleID: id, Default: true, Enabled: enabled, Actions: acts, Conditions: conds} } // Defaults returns the server-default ruleset for a user, in the spec's // order. It is built per user because three of the rules name the user: // the invite rule matches their user ID, the mention rule looks for it in // m.mentions, and the content rule looks for their localpart in the body. func Defaults(userID string) Ruleset { localpart := strings.TrimPrefix(userID, "@") if i := strings.IndexByte(localpart, ':'); i >= 0 { localpart = localpart[:i] } quotedUser, _ := json.Marshal(userID) rs := Ruleset{ Override: []Rule{ override(RuleMaster, false, actions()), override(RuleSuppressNotices, true, actions(), match("content.msgtype", "m.notice")), override(RuleInviteForMe, true, actions(actNotify, actSoundDefault), match("type", "m.room.member"), match("content.membership", "invite"), match("state_key", userID)), override(RuleMemberEvent, true, actions(), match("type", "m.room.member")), override(RuleIsUserMention, true, actions(actNotify, actSoundDefault, actHighlight), Condition{Kind: CondEventPropertyContains, Key: `content.m\.mentions.user_ids`, Value: quotedUser}), override(RuleContainsDisplayName, true, actions(actNotify, actSoundDefault, actHighlight), Condition{Kind: CondContainsDisplayName}), override(RuleIsRoomMention, true, actions(actNotify, actHighlight), propertyIs(`content.m\.mentions.room`, json.RawMessage(`true`)), Condition{Kind: CondSenderNotificationPermission, Key: "room"}), override(RuleRoomNotif, true, actions(actNotify, actHighlight), Condition{Kind: CondSenderNotificationPermission, Key: "room"}, match("content.body", "@room")), override(RuleTombstone, true, actions(actNotify, actHighlight), match("type", "m.room.tombstone"), match("state_key", "")), override(RuleReaction, true, actions(), match("type", "m.reaction")), override(RuleServerACL, true, actions(), match("type", "m.room.server_acl"), match("state_key", "")), override(RuleSuppressEdits, true, actions(), propertyIs(`content.m\.relates_to.rel_type`, json.RawMessage(`"m.replace"`))), }, Content: []Rule{{ RuleID: RuleContainsUserName, Default: true, Enabled: true, Actions: actions(actNotify, actSoundDefault, actHighlight), Pattern: &localpart, }}, Underride: []Rule{ override(RuleCall, true, actions(actNotify, actSoundRing), match("type", "m.call.invite")), override(RuleEncryptedRoomOneToOne, true, actions(actNotify, actSoundDefault), Condition{Kind: CondRoomMemberCount, Is: "2"}, match("type", "m.room.encrypted")), override(RuleRoomOneToOne, true, actions(actNotify, actSoundDefault), Condition{Kind: CondRoomMemberCount, Is: "2"}, match("type", "m.room.message")), override(RuleMessage, true, actions(actNotify), match("type", "m.room.message")), override(RuleEncrypted, true, actions(actNotify), match("type", "m.room.encrypted")), }, } rs.normalise() return rs }