aboutsummaryrefslogtreecommitdiff
path: root/matrix/pushrules
diff options
context:
space:
mode:
authorTulir Asokan <tulir@maunium.net>2018-03-21 23:29:58 +0200
committerTulir Asokan <tulir@maunium.net>2018-03-21 23:29:58 +0200
commit9fd67102ad2cca16c092e23ffd928b77ab08d7e0 (patch)
tree2c34025e564806cb8f9faad0c15e25a0e6dec3ed /matrix/pushrules
parentb4902d4edb27baf59b21747117d93db4e0e4e96c (diff)
Refactoring and godocs
Diffstat (limited to 'matrix/pushrules')
-rw-r--r--matrix/pushrules/action.go135
-rw-r--r--matrix/pushrules/condition.go147
-rw-r--r--matrix/pushrules/doc.go2
-rw-r--r--matrix/pushrules/pushrules.go42
-rw-r--r--matrix/pushrules/rule.go150
-rw-r--r--matrix/pushrules/ruleset.go87
6 files changed, 563 insertions, 0 deletions
diff --git a/matrix/pushrules/action.go b/matrix/pushrules/action.go
new file mode 100644
index 0000000..1de973f
--- /dev/null
+++ b/matrix/pushrules/action.go
@@ -0,0 +1,135 @@
+// gomuks - A terminal Matrix client written in Go.
+// Copyright (C) 2018 Tulir Asokan
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package pushrules
+
+import "encoding/json"
+
+// PushActionType is the type of a PushAction
+type PushActionType string
+
+// The allowed push action types as specified in spec section 11.12.1.4.1.
+const (
+ ActionNotify PushActionType = "notify"
+ ActionDontNotify PushActionType = "dont_notify"
+ ActionCoalesce PushActionType = "coalesce"
+ ActionSetTweak PushActionType = "set_tweak"
+)
+
+// PushActionTweak is the type of the tweak in SetTweak push actions.
+type PushActionTweak string
+
+// The allowed tweak types as specified in spec section 11.12.1.4.1.1.
+const (
+ TweakSound PushActionTweak = "sound"
+ TweakHighlight PushActionTweak = "highlight"
+)
+
+// PushActionArray is an array of PushActions.
+type PushActionArray []*PushAction
+
+// PushActionArrayShould contains the important information parsed from a PushActionArray.
+type PushActionArrayShould struct {
+ // Whether or not the array contained a Notify, DontNotify or Coalesce action type.
+ NotifySpecified bool
+ // Whether or not the event in question should trigger a notification.
+ Notify bool
+ // Whether or not the event in question should be highlighted.
+ Highlight bool
+
+ // Whether or not the event in question should trigger a sound alert.
+ PlaySound bool
+ // The name of the sound to play if PlaySound is true.
+ SoundName string
+}
+
+// Should parses this push action array and returns the relevant details wrapped in a PushActionArrayShould struct.
+func (actions PushActionArray) Should() (should PushActionArrayShould) {
+ for _, action := range actions {
+ switch action.Action {
+ case ActionNotify, ActionCoalesce:
+ should.Notify = true
+ should.NotifySpecified = true
+ case ActionDontNotify:
+ should.Notify = false
+ should.NotifySpecified = true
+ case ActionSetTweak:
+ switch action.Tweak {
+ case TweakHighlight:
+ var ok bool
+ should.Highlight, ok = action.Value.(bool)
+ if !ok {
+ // Highlight value not specified, so assume true since the tweak is set.
+ should.Highlight = true
+ }
+ case TweakSound:
+ should.SoundName = action.Value.(string)
+ should.PlaySound = len(should.SoundName) > 0
+ }
+ }
+ }
+ return
+}
+
+// PushAction is a single action that should be triggered when receiving a message.
+type PushAction struct {
+ Action PushActionType
+ Tweak PushActionTweak
+ Value interface{}
+}
+
+// UnmarshalJSON parses JSON into this PushAction.
+//
+// * If the JSON is a single string, the value is stored in the Action field.
+// * If the JSON is an object with the set_tweak field, Action will be set to
+// "set_tweak", Tweak will be set to the value of the set_tweak field and
+// and Value will be set to the value of the value field.
+// * In any other case, the function does nothing.
+func (action *PushAction) UnmarshalJSON(raw []byte) error {
+ var data interface{}
+
+ err := json.Unmarshal(raw, &data)
+ if err != nil {
+ return err
+ }
+
+ switch val := data.(type) {
+ case string:
+ action.Action = PushActionType(val)
+ case map[string]interface{}:
+ tweak, ok := val["set_tweak"].(string)
+ if ok {
+ action.Action = ActionSetTweak
+ action.Tweak = PushActionTweak(tweak)
+ action.Value, _ = val["value"]
+ }
+ }
+ return nil
+}
+
+// MarshalJSON is the reverse of UnmarshalJSON()
+func (action *PushAction) MarshalJSON() (raw []byte, err error) {
+ if action.Action == ActionSetTweak {
+ data := map[string]interface{}{
+ "set_tweak": action.Tweak,
+ "value": action.Value,
+ }
+ return json.Marshal(&data)
+ } else {
+ data := string(action.Action)
+ return json.Marshal(&data)
+ }
+}
diff --git a/matrix/pushrules/condition.go b/matrix/pushrules/condition.go
new file mode 100644
index 0000000..ecbf5b2
--- /dev/null
+++ b/matrix/pushrules/condition.go
@@ -0,0 +1,147 @@
+// gomuks - A terminal Matrix client written in Go.
+// Copyright (C) 2018 Tulir Asokan
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package pushrules
+
+import (
+ "regexp"
+ "strconv"
+ "strings"
+
+ "github.com/zyedidia/glob"
+ "maunium.net/go/gomatrix"
+ "maunium.net/go/gomuks/matrix/room"
+)
+
+// PushCondKind is the type of a push condition.
+type PushCondKind string
+
+// The allowed push condition kinds as specified in section 11.12.1.4.3 of r0.3.0 of the Client-Server API.
+const (
+ KindEventMatch PushCondKind = "event_match"
+ KindContainsDisplayName PushCondKind = "contains_display_name"
+ KindRoomMemberCount PushCondKind = "room_member_count"
+)
+
+// PushCondition wraps a condition that is required for a specific PushRule to be used.
+type PushCondition struct {
+ // The type of the condition.
+ Kind PushCondKind `json:"kind"`
+ // The dot-separated field of the event to match. Only applicable if kind is EventMatch.
+ Key string `json:"key,omitempty"`
+ // The glob-style pattern to match the field against. Only applicable if kind is EventMatch.
+ Pattern string `json:"pattern,omitempty"`
+ // The condition that needs to be fulfilled for RoomMemberCount-type conditions.
+ // A decimal integer optionally prefixed by ==, <, >, >= or <=. Prefix "==" is assumed if no prefix found.
+ MemberCountCondition string `json:"is,omitempty"`
+}
+
+// MemberCountFilterRegex is the regular expression to parse the MemberCountCondition of PushConditions.
+var MemberCountFilterRegex = regexp.MustCompile("^(==|[<>]=?)?([0-9]+)$")
+
+// Match checks if this condition is fulfilled for the given event in the given room.
+func (cond *PushCondition) Match(room *rooms.Room, event *gomatrix.Event) bool {
+ switch cond.Kind {
+ case KindEventMatch:
+ return cond.matchValue(room, event)
+ case KindContainsDisplayName:
+ return cond.matchDisplayName(room, event)
+ case KindRoomMemberCount:
+ return cond.matchMemberCount(room, event)
+ default:
+ return false
+ }
+}
+
+func (cond *PushCondition) matchValue(room *rooms.Room, event *gomatrix.Event) bool {
+ index := strings.IndexRune(cond.Key, '.')
+ key := cond.Key
+ subkey := ""
+ if index > 0 {
+ subkey = key[index+1:]
+ key = key[0:index]
+ }
+
+ pattern, err := glob.Compile(cond.Pattern)
+ if err != nil {
+ return false
+ }
+
+ switch key {
+ case "type":
+ return pattern.MatchString(event.Type)
+ case "sender":
+ return pattern.MatchString(event.Sender)
+ case "room_id":
+ return pattern.MatchString(event.RoomID)
+ case "state_key":
+ if event.StateKey == nil {
+ return cond.Pattern == ""
+ }
+ return pattern.MatchString(*event.StateKey)
+ case "content":
+ val, _ := event.Content[subkey].(string)
+ return pattern.MatchString(val)
+ default:
+ return false
+ }
+}
+
+func (cond *PushCondition) matchDisplayName(room *rooms.Room, event *gomatrix.Event) bool {
+ member := room.GetMember(room.SessionUserID)
+ if member == nil {
+ return false
+ }
+ text, _ := event.Content["body"].(string)
+ return strings.Contains(text, member.DisplayName)
+}
+
+func (cond *PushCondition) matchMemberCount(room *rooms.Room, event *gomatrix.Event) bool {
+ groupGroups := MemberCountFilterRegex.FindAllStringSubmatch(cond.MemberCountCondition, -1)
+ if len(groupGroups) != 1 {
+ return false
+ }
+
+ operator := "=="
+ wantedMemberCount := 0
+
+ group := groupGroups[0]
+ if len(group) == 0 {
+ return false
+ } else if len(group) == 1 {
+ wantedMemberCount, _ = strconv.Atoi(group[0])
+ } else {
+ operator = group[0]
+ wantedMemberCount, _ = strconv.Atoi(group[1])
+ }
+
+ memberCount := len(room.GetMembers())
+
+ switch operator {
+ case "==":
+ return wantedMemberCount == memberCount
+ case ">":
+ return wantedMemberCount > memberCount
+ case ">=":
+ return wantedMemberCount >= memberCount
+ case "<":
+ return wantedMemberCount < memberCount
+ case "<=":
+ return wantedMemberCount <= memberCount
+ default:
+ return false
+ }
+}
diff --git a/matrix/pushrules/doc.go b/matrix/pushrules/doc.go
new file mode 100644
index 0000000..19cd774
--- /dev/null
+++ b/matrix/pushrules/doc.go
@@ -0,0 +1,2 @@
+// Package pushrules contains utilities to parse push notification rules.
+package pushrules
diff --git a/matrix/pushrules/pushrules.go b/matrix/pushrules/pushrules.go
new file mode 100644
index 0000000..bfcb4ef
--- /dev/null
+++ b/matrix/pushrules/pushrules.go
@@ -0,0 +1,42 @@
+package pushrules
+
+import (
+
+"encoding/json"
+"net/url"
+
+"maunium.net/go/gomatrix"
+
+)
+
+// GetPushRules returns the push notification rules for the global scope.
+func GetPushRules(client *gomatrix.Client) (*PushRuleset, error) {
+ return GetScopedPushRules(client, "global")
+}
+
+// GetScopedPushRules returns the push notification rules for the given scope.
+func GetScopedPushRules(client *gomatrix.Client, scope string) (resp *PushRuleset, err error) {
+ u, _ := url.Parse(client.BuildURL("pushrules", scope))
+ // client.BuildURL returns the URL without a trailing slash, but the pushrules endpoint requires the slash.
+ u.Path += "/"
+ _, err = client.MakeRequest("GET", u.String(), nil, &resp)
+ return
+}
+
+// EventToPushRules converts a m.push_rules event to a PushRuleset by passing the data through JSON.
+func EventToPushRules(event *gomatrix.Event) (*PushRuleset, error) {
+ content, _ := event.Content["global"]
+ raw, err := json.Marshal(content)
+ if err != nil {
+ return nil, err
+ }
+
+ ruleset := &PushRuleset{}
+ err = json.Unmarshal(raw, ruleset)
+ if err != nil {
+ return nil, err
+ }
+
+ return ruleset, nil
+}
+
diff --git a/matrix/pushrules/rule.go b/matrix/pushrules/rule.go
new file mode 100644
index 0000000..067bc95
--- /dev/null
+++ b/matrix/pushrules/rule.go
@@ -0,0 +1,150 @@
+// gomuks - A terminal Matrix client written in Go.
+// Copyright (C) 2018 Tulir Asokan
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package pushrules
+
+import (
+ "github.com/zyedidia/glob"
+ "maunium.net/go/gomatrix"
+ "maunium.net/go/gomuks/matrix/room"
+)
+
+type PushRuleArray []*PushRule
+
+func (rules PushRuleArray) setType(typ PushRuleType) PushRuleArray {
+ for _, rule := range rules {
+ rule.Type = typ
+ }
+ return rules
+}
+
+func (rules PushRuleArray) GetActions(room *rooms.Room, event *gomatrix.Event) PushActionArray {
+ for _, rule := range rules {
+ if !rule.Match(room, event) {
+ continue
+ }
+ return rule.Actions
+ }
+ return nil
+}
+
+type PushRuleMap struct {
+ Map map[string]*PushRule
+ Type PushRuleType
+}
+
+func (rules PushRuleArray) setTypeAndMap(typ PushRuleType) PushRuleMap {
+ data := PushRuleMap{
+ Map: make(map[string]*PushRule),
+ Type: typ,
+ }
+ for _, rule := range rules {
+ rule.Type = typ
+ data.Map[rule.RuleID] = rule
+ }
+ return data
+}
+
+func (ruleMap PushRuleMap) GetActions(room *rooms.Room, event *gomatrix.Event) PushActionArray {
+ var rule *PushRule
+ var found bool
+ switch ruleMap.Type {
+ case RoomRule:
+ rule, found = ruleMap.Map[event.RoomID]
+ case SenderRule:
+ rule, found = ruleMap.Map[event.Sender]
+ }
+ if found && rule.Match(room, event) {
+ return rule.Actions
+ }
+ return nil
+}
+
+func (ruleMap PushRuleMap) unmap() PushRuleArray {
+ array := make(PushRuleArray, len(ruleMap.Map))
+ index := 0
+ for _, rule := range ruleMap.Map {
+ array[index] = rule
+ index++
+ }
+ return array
+}
+
+type PushRuleType string
+
+const (
+ OverrideRule PushRuleType = "override"
+ ContentRule PushRuleType = "content"
+ RoomRule PushRuleType = "room"
+ SenderRule PushRuleType = "sender"
+ UnderrideRule PushRuleType = "underride"
+)
+
+type PushRule struct {
+ // The type of this rule.
+ Type PushRuleType `json:"-"`
+ // The ID of this rule.
+ // For room-specific rules and user-specific rules, this is the room or user ID (respectively)
+ // For other types of rules, this doesn't affect anything.
+ RuleID string `json:"rule_id"`
+ // The actions this rule should trigger when matched.
+ Actions PushActionArray `json:"actions"`
+ // Whether this is a default rule, or has been set explicitly.
+ Default bool `json:"default"`
+ // Whether or not this push rule is enabled.
+ Enabled bool `json:"enabled"`
+ // The conditions to match in order to trigger this rule.
+ // Only applicable to generic underride/override rules.
+ Conditions []*PushCondition `json:"conditions,omitempty"`
+ // Pattern for content-specific push rules
+ Pattern string `json:"pattern,omitempty"`
+}
+
+func (rule *PushRule) Match(room *rooms.Room, event *gomatrix.Event) bool {
+ if !rule.Enabled {
+ return false
+ }
+ switch rule.Type {
+ case OverrideRule, UnderrideRule:
+ return rule.matchConditions(room, event)
+ case ContentRule:
+ return rule.matchPattern(room, event)
+ case RoomRule:
+ return rule.RuleID == event.RoomID
+ case SenderRule:
+ return rule.RuleID == event.Sender
+ default:
+ return false
+ }
+}
+
+func (rule *PushRule) matchConditions(room *rooms.Room, event *gomatrix.Event) bool {
+ for _, cond := range rule.Conditions {
+ if !cond.Match(room, event) {
+ return false
+ }
+ }
+ return true
+}
+
+func (rule *PushRule) matchPattern(room *rooms.Room, event *gomatrix.Event) bool {
+ pattern, err := glob.Compile(rule.Pattern)
+ if err != nil {
+ return false
+ }
+ text, _ := event.Content["body"].(string)
+ return pattern.MatchString(text)
+}
diff --git a/matrix/pushrules/ruleset.go b/matrix/pushrules/ruleset.go
new file mode 100644
index 0000000..6708b70
--- /dev/null
+++ b/matrix/pushrules/ruleset.go
@@ -0,0 +1,87 @@
+// gomuks - A terminal Matrix client written in Go.
+// Copyright (C) 2018 Tulir Asokan
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package pushrules
+
+import (
+ "encoding/json"
+
+ "maunium.net/go/gomatrix"
+ "maunium.net/go/gomuks/matrix/room"
+)
+
+type PushRuleset struct {
+ Override PushRuleArray
+ Content PushRuleArray
+ Room PushRuleMap
+ Sender PushRuleMap
+ Underride PushRuleArray
+}
+
+type rawPushRuleset struct {
+ Override PushRuleArray `json:"override"`
+ Content PushRuleArray `json:"content"`
+ Room PushRuleArray `json:"room"`
+ Sender PushRuleArray `json:"sender"`
+ Underride PushRuleArray `json:"underride"`
+}
+
+func (rs *PushRuleset) UnmarshalJSON(raw []byte) (err error) {
+ data := rawPushRuleset{}
+ err = json.Unmarshal(raw, &data)
+ if err != nil {
+ return
+ }
+
+ rs.Override = data.Override.setType(OverrideRule)
+ rs.Content = data.Content.setType(ContentRule)
+ rs.Room = data.Room.setTypeAndMap(RoomRule)
+ rs.Sender = data.Sender.setTypeAndMap(SenderRule)
+ rs.Underride = data.Underride.setType(UnderrideRule)
+ return
+}
+
+func (rs *PushRuleset) MarshalJSON() ([]byte, error) {
+ data := rawPushRuleset{
+ Override: rs.Override,
+ Content: rs.Content,
+ Room: rs.Room.unmap(),
+ Sender: rs.Sender.unmap(),
+ Underride: rs.Underride,
+ }
+ return json.Marshal(&data)
+}
+
+var DefaultPushActions = make(PushActionArray, 0)
+
+func (rs *PushRuleset) GetActions(room *rooms.Room, event *gomatrix.Event) (match PushActionArray) {
+ if match = rs.Override.GetActions(room, event); match != nil {
+ return
+ }
+ if match = rs.Content.GetActions(room, event); match != nil {
+ return
+ }
+ if match = rs.Room.GetActions(room, event); match != nil {
+ return
+ }
+ if match = rs.Sender.GetActions(room, event); match != nil {
+ return
+ }
+ if match = rs.Underride.GetActions(room, event); match != nil {
+ return
+ }
+ return DefaultPushActions
+}