aboutsummaryrefslogtreecommitdiff
path: root/matrix
diff options
context:
space:
mode:
authorTulir Asokan <tulir@maunium.net>2018-03-21 18:46:19 +0200
committerTulir Asokan <tulir@maunium.net>2018-03-21 18:46:25 +0200
commit35b6c7bd276d2a6c7f09163d757a1c3cb885da79 (patch)
tree3cda5627ce5445b0a76ee3fb72551bf326943e57 /matrix
parent7994c289aae7662fee9b86f9424d230c7b612b3d (diff)
Add external debug file, refactoring and push rule parser
Diffstat (limited to 'matrix')
-rw-r--r--matrix/ext/doc.go18
-rw-r--r--matrix/ext/pushrules.go376
-rw-r--r--matrix/matrix.go26
-rw-r--r--matrix/room/member.go2
-rw-r--r--matrix/room/room.go2
5 files changed, 420 insertions, 4 deletions
diff --git a/matrix/ext/doc.go b/matrix/ext/doc.go
new file mode 100644
index 0000000..21b818c
--- /dev/null
+++ b/matrix/ext/doc.go
@@ -0,0 +1,18 @@
+// 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 gomx_ext contains extensions to the gomatrix package.
+package gomx_ext
diff --git a/matrix/ext/pushrules.go b/matrix/ext/pushrules.go
new file mode 100644
index 0000000..6cb16d2
--- /dev/null
+++ b/matrix/ext/pushrules.go
@@ -0,0 +1,376 @@
+package gomx_ext
+
+import (
+ "encoding/json"
+ "net/url"
+ "regexp"
+ "strconv"
+ "strings"
+
+ "github.com/zyedidia/glob"
+ "maunium.net/go/gomatrix"
+ "maunium.net/go/gomuks/matrix/room"
+)
+
+// GetPushRules returns the push notification rules for the given scope.
+func GetPushRules(client *gomatrix.Client) (resp *PushRuleset, err error) {
+ u, _ := url.Parse(client.BuildURL("pushrules", "global"))
+ u.Path += "/"
+ _, err = client.MakeRequest("GET", u.String(), nil, &resp)
+ return
+}
+
+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)
+}
+
+func (rs *PushRuleset) GetActions(room *rooms.Room, event *gomatrix.Event) (match []*PushAction) {
+ 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
+}
+
+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) []*PushAction {
+ 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) []*PushAction {
+ 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 []*PushAction `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)
+}
+
+type PushActionType string
+
+const (
+ ActionNotify PushActionType = "notify"
+ ActionDontNotify PushActionType = "dont_notify"
+ ActionCoalesce PushActionType = "coalesce"
+ ActionSetTweak PushActionType = "set_tweak"
+)
+
+type PushActionTweak string
+
+const (
+ TweakSound PushActionTweak = "sound"
+ TweakHighlight PushActionTweak = "highlight"
+)
+
+type PushAction struct {
+ Action PushActionType
+ Tweak PushActionTweak
+ Value string
+}
+
+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"].(string)
+ }
+ }
+ return nil
+}
+
+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)
+ }
+}
+
+type PushKind string
+
+const (
+ KindEventMatch PushKind = "event_match"
+ KindContainsDisplayName PushKind = "contains_display_name"
+ KindRoomMemberCount PushKind = "room_member_count"
+)
+
+type PushCondition struct {
+ Kind PushKind `json:"kind"`
+ Key string `json:"key,omitempty"`
+ Pattern string `json:"pattern,omitempty"`
+ Is string `json:"string,omitempty"`
+}
+
+var MemberCountFilterRegex = regexp.MustCompile("^(==|[<>]=?)?([0-9]+)$")
+
+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 true
+ }
+}
+
+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.Owner)
+ 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.Is, -1)
+ if len(groupGroups) != 1 {
+ return true
+ }
+
+ operator := "=="
+ wantedMemberCount := 0
+
+ group := groupGroups[0]
+ if len(group) == 0 {
+ return true
+ } 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/matrix.go b/matrix/matrix.go
index 556d567..7dbbbe1 100644
--- a/matrix/matrix.go
+++ b/matrix/matrix.go
@@ -24,7 +24,8 @@ import (
"maunium.net/go/gomatrix"
"maunium.net/go/gomuks/config"
"maunium.net/go/gomuks/interface"
- rooms "maunium.net/go/gomuks/matrix/room"
+ "maunium.net/go/gomuks/matrix/ext"
+ "maunium.net/go/gomuks/matrix/room"
"maunium.net/go/gomuks/notification"
"maunium.net/go/gomuks/ui/debug"
"maunium.net/go/gomuks/ui/types"
@@ -120,13 +121,20 @@ func (c *Container) Client() *gomatrix.Client {
}
func (c *Container) UpdatePushRules() {
- resp, err := c.client.PushRules()
+ resp, err := gomx_ext.GetPushRules(c.client)
if err != nil {
debug.Print("Failed to fetch push rules:", err)
}
c.config.Session.PushRules = resp
}
+func (c *Container) PushRules() *gomx_ext.PushRuleset {
+ if c.config.Session.PushRules == nil {
+ c.UpdatePushRules()
+ }
+ return c.config.Session.PushRules
+}
+
func (c *Container) UpdateRoomList() {
resp, err := c.client.JoinedRooms()
if err != nil {
@@ -200,6 +208,20 @@ func (c *Container) NotifyMessage(room *rooms.Room, message *types.Message) {
func (c *Container) HandleMessage(evt *gomatrix.Event) {
room, message := c.ui.MainView().ProcessMessageEvent(evt)
if room != nil {
+ match := c.PushRules().GetActions(room.Room, evt)
+
+ var buf strings.Builder
+ buf.WriteRune('[')
+ for i, rule := range match {
+ fmt.Fprintf(&buf, "{%s, %s, %s}", rule.Action, rule.Tweak, rule.Value)
+ if i < len(match)-1 {
+ buf.WriteRune(',')
+ buf.WriteRune(' ')
+ }
+ }
+ buf.WriteRune(']')
+ debug.Print(buf.String())
+
c.NotifyMessage(room.Room, message)
room.AddMessage(message, widget.AppendMessage)
c.ui.Render()
diff --git a/matrix/room/member.go b/matrix/room/member.go
index 474d2fd..3b3a30c 100644
--- a/matrix/room/member.go
+++ b/matrix/room/member.go
@@ -14,7 +14,7 @@
// 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 room
+package rooms
import (
"maunium.net/go/gomatrix"
diff --git a/matrix/room/room.go b/matrix/room/room.go
index 6bafbfa..92d6c5a 100644
--- a/matrix/room/room.go
+++ b/matrix/room/room.go
@@ -14,7 +14,7 @@
// 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 room
+package rooms
import (
"fmt"