Refactoring and godocs
This commit is contained in:
135
matrix/pushrules/action.go
Normal file
135
matrix/pushrules/action.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
147
matrix/pushrules/condition.go
Normal file
147
matrix/pushrules/condition.go
Normal file
@ -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
|
||||
}
|
||||
}
|
2
matrix/pushrules/doc.go
Normal file
2
matrix/pushrules/doc.go
Normal file
@ -0,0 +1,2 @@
|
||||
// Package pushrules contains utilities to parse push notification rules.
|
||||
package pushrules
|
42
matrix/pushrules/pushrules.go
Normal file
42
matrix/pushrules/pushrules.go
Normal file
@ -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
|
||||
}
|
||||
|
150
matrix/pushrules/rule.go
Normal file
150
matrix/pushrules/rule.go
Normal file
@ -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)
|
||||
}
|
87
matrix/pushrules/ruleset.go
Normal file
87
matrix/pushrules/ruleset.go
Normal file
@ -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
|
||||
}
|
Reference in New Issue
Block a user