Refactoring and godocs
This commit is contained in:
parent
b4902d4edb
commit
9fd67102ad
@ -27,8 +27,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
MXID string `yaml:"mxid"`
|
UserID string `yaml:"mxid"`
|
||||||
HS string `yaml:"homeserver"`
|
HS string `yaml:"homeserver"`
|
||||||
|
|
||||||
dir string `yaml:"-"`
|
dir string `yaml:"-"`
|
||||||
Session *Session `yaml:"-"`
|
Session *Session `yaml:"-"`
|
||||||
|
@ -22,19 +22,19 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/gomatrix"
|
||||||
"maunium.net/go/gomuks/matrix/ext"
|
"maunium.net/go/gomuks/matrix/pushrules"
|
||||||
rooms "maunium.net/go/gomuks/matrix/room"
|
rooms "maunium.net/go/gomuks/matrix/room"
|
||||||
"maunium.net/go/gomuks/ui/debug"
|
"maunium.net/go/gomuks/ui/debug"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Session struct {
|
type Session struct {
|
||||||
MXID string `json:"-"`
|
UserID string `json:"-"`
|
||||||
path string `json:"-"`
|
path string `json:"-"`
|
||||||
AccessToken string
|
AccessToken string
|
||||||
NextBatch string
|
NextBatch string
|
||||||
FilterID string
|
FilterID string
|
||||||
Rooms map[string]*rooms.Room
|
Rooms map[string]*rooms.Room
|
||||||
PushRules *gomx_ext.PushRuleset
|
PushRules *pushrules.PushRuleset
|
||||||
}
|
}
|
||||||
|
|
||||||
func (config *Config) LoadSession(mxid string) error {
|
func (config *Config) LoadSession(mxid string) error {
|
||||||
@ -44,9 +44,9 @@ func (config *Config) LoadSession(mxid string) error {
|
|||||||
|
|
||||||
func (config *Config) NewSession(mxid string) *Session {
|
func (config *Config) NewSession(mxid string) *Session {
|
||||||
return &Session{
|
return &Session{
|
||||||
MXID: mxid,
|
UserID: mxid,
|
||||||
path: filepath.Join(config.dir, mxid+".session"),
|
path: filepath.Join(config.dir, mxid+".session"),
|
||||||
Rooms: make(map[string]*rooms.Room),
|
Rooms: make(map[string]*rooms.Room),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ func (s *Session) Load() error {
|
|||||||
func (s *Session) Save() error {
|
func (s *Session) Save() error {
|
||||||
data, err := json.Marshal(s)
|
data, err := json.Marshal(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Print("Failed to marshal session of", s.MXID, err)
|
debug.Print("Failed to marshal session of", s.UserID, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +99,7 @@ func (s *Session) LoadNextBatch(_ string) string {
|
|||||||
func (s *Session) GetRoom(mxid string) *rooms.Room {
|
func (s *Session) GetRoom(mxid string) *rooms.Room {
|
||||||
room, _ := s.Rooms[mxid]
|
room, _ := s.Rooms[mxid]
|
||||||
if room == nil {
|
if room == nil {
|
||||||
room = rooms.NewRoom(mxid, s.MXID)
|
room = rooms.NewRoom(mxid, s.UserID)
|
||||||
s.Rooms[room.ID] = room
|
s.Rooms[room.ID] = room
|
||||||
}
|
}
|
||||||
return room
|
return room
|
||||||
|
@ -54,11 +54,11 @@ func NewGomuks(enableDebug bool) *Gomuks {
|
|||||||
|
|
||||||
gmx.config = config.NewConfig(configDir)
|
gmx.config = config.NewConfig(configDir)
|
||||||
gmx.ui = ui.NewGomuksUI(gmx)
|
gmx.ui = ui.NewGomuksUI(gmx)
|
||||||
gmx.matrix = matrix.NewMatrixContainer(gmx)
|
gmx.matrix = matrix.NewContainer(gmx)
|
||||||
|
|
||||||
gmx.config.Load()
|
gmx.config.Load()
|
||||||
if len(gmx.config.MXID) > 0 {
|
if len(gmx.config.UserID) > 0 {
|
||||||
gmx.config.LoadSession(gmx.config.MXID)
|
gmx.config.LoadSession(gmx.config.UserID)
|
||||||
}
|
}
|
||||||
|
|
||||||
gmx.matrix.InitClient()
|
gmx.matrix.InitClient()
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
// 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
|
|
@ -1,438 +0,0 @@
|
|||||||
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 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
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 PushActionArray []*PushAction
|
|
||||||
|
|
||||||
type PushActionArrayShould struct {
|
|
||||||
NotifySpecified bool
|
|
||||||
Notify bool
|
|
||||||
Highlight bool
|
|
||||||
|
|
||||||
PlaySound bool
|
|
||||||
SoundName string
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
type PushAction struct {
|
|
||||||
Action PushActionType
|
|
||||||
Tweak PushActionTweak
|
|
||||||
Value interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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.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 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
|
|
||||||
}
|
|
||||||
}
|
|
@ -25,13 +25,16 @@ import (
|
|||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/gomatrix"
|
||||||
"maunium.net/go/gomuks/config"
|
"maunium.net/go/gomuks/config"
|
||||||
"maunium.net/go/gomuks/interface"
|
"maunium.net/go/gomuks/interface"
|
||||||
"maunium.net/go/gomuks/matrix/ext"
|
"maunium.net/go/gomuks/matrix/pushrules"
|
||||||
"maunium.net/go/gomuks/matrix/room"
|
"maunium.net/go/gomuks/matrix/room"
|
||||||
"maunium.net/go/gomuks/notification"
|
"maunium.net/go/gomuks/notification"
|
||||||
"maunium.net/go/gomuks/ui/debug"
|
"maunium.net/go/gomuks/ui/debug"
|
||||||
"maunium.net/go/gomuks/ui/widget"
|
"maunium.net/go/gomuks/ui/widget"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Container is a wrapper for a gomatrix Client and some other stuff.
|
||||||
|
//
|
||||||
|
// It is used for all Matrix calls from the UI and Matrix event handlers.
|
||||||
type Container struct {
|
type Container struct {
|
||||||
client *gomatrix.Client
|
client *gomatrix.Client
|
||||||
gmx ifc.Gomuks
|
gmx ifc.Gomuks
|
||||||
@ -43,7 +46,8 @@ type Container struct {
|
|||||||
typing int64
|
typing int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMatrixContainer(gmx ifc.Gomuks) *Container {
|
// NewContainer creates a new Container for the given Gomuks instance.
|
||||||
|
func NewContainer(gmx ifc.Gomuks) *Container {
|
||||||
c := &Container{
|
c := &Container{
|
||||||
config: gmx.Config(),
|
config: gmx.Config(),
|
||||||
ui: gmx.UI(),
|
ui: gmx.UI(),
|
||||||
@ -53,6 +57,7 @@ func NewMatrixContainer(gmx ifc.Gomuks) *Container {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InitClient initializes the gomatrix client and connects to the homeserver specified in the config.
|
||||||
func (c *Container) InitClient() error {
|
func (c *Container) InitClient() error {
|
||||||
if len(c.config.HS) == 0 {
|
if len(c.config.HS) == 0 {
|
||||||
return fmt.Errorf("no homeserver in config")
|
return fmt.Errorf("no homeserver in config")
|
||||||
@ -66,7 +71,7 @@ func (c *Container) InitClient() error {
|
|||||||
var mxid, accessToken string
|
var mxid, accessToken string
|
||||||
if c.config.Session != nil {
|
if c.config.Session != nil {
|
||||||
accessToken = c.config.Session.AccessToken
|
accessToken = c.config.Session.AccessToken
|
||||||
mxid = c.config.MXID
|
mxid = c.config.UserID
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@ -77,16 +82,18 @@ func (c *Container) InitClient() error {
|
|||||||
|
|
||||||
c.stop = make(chan bool, 1)
|
c.stop = make(chan bool, 1)
|
||||||
|
|
||||||
if c.config.Session != nil && len(c.config.Session.AccessToken) > 0 {
|
if c.config.Session != nil && len(accessToken) > 0 {
|
||||||
go c.Start()
|
go c.Start()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialized returns whether or not the gomatrix client is initialized (see InitClient())
|
||||||
func (c *Container) Initialized() bool {
|
func (c *Container) Initialized() bool {
|
||||||
return c.client != nil
|
return c.client != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Login sends a password login request with the given username and password.
|
||||||
func (c *Container) Login(user, password string) error {
|
func (c *Container) Login(user, password string) error {
|
||||||
resp, err := c.client.Login(&gomatrix.ReqLogin{
|
resp, err := c.client.Login(&gomatrix.ReqLogin{
|
||||||
Type: "m.login.password",
|
Type: "m.login.password",
|
||||||
@ -97,7 +104,7 @@ func (c *Container) Login(user, password string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.client.SetCredentials(resp.UserID, resp.AccessToken)
|
c.client.SetCredentials(resp.UserID, resp.AccessToken)
|
||||||
c.config.MXID = resp.UserID
|
c.config.UserID = resp.UserID
|
||||||
c.config.Save()
|
c.config.Save()
|
||||||
|
|
||||||
c.config.Session = c.config.NewSession(resp.UserID)
|
c.config.Session = c.config.NewSession(resp.UserID)
|
||||||
@ -109,6 +116,7 @@ func (c *Container) Login(user, password string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop stops the Matrix syncer.
|
||||||
func (c *Container) Stop() {
|
func (c *Container) Stop() {
|
||||||
if c.running {
|
if c.running {
|
||||||
c.stop <- true
|
c.stop <- true
|
||||||
@ -116,26 +124,30 @@ func (c *Container) Stop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Client returns the underlying gomatrix client object.
|
||||||
func (c *Container) Client() *gomatrix.Client {
|
func (c *Container) Client() *gomatrix.Client {
|
||||||
return c.client
|
return c.client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdatePushRules fetches the push notification rules from the server and stores them in the current Session object.
|
||||||
func (c *Container) UpdatePushRules() {
|
func (c *Container) UpdatePushRules() {
|
||||||
debug.Print("Updating push rules...")
|
debug.Print("Updating push rules...")
|
||||||
resp, err := gomx_ext.GetPushRules(c.client)
|
resp, err := pushrules.GetPushRules(c.client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Print("Failed to fetch push rules:", err)
|
debug.Print("Failed to fetch push rules:", err)
|
||||||
}
|
}
|
||||||
c.config.Session.PushRules = resp
|
c.config.Session.PushRules = resp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) PushRules() *gomx_ext.PushRuleset {
|
// PushRules returns the push notification rules. If no push rules are cached, UpdatePushRules() will be called first.
|
||||||
|
func (c *Container) PushRules() *pushrules.PushRuleset {
|
||||||
if c.config.Session.PushRules == nil {
|
if c.config.Session.PushRules == nil {
|
||||||
c.UpdatePushRules()
|
c.UpdatePushRules()
|
||||||
}
|
}
|
||||||
return c.config.Session.PushRules
|
return c.config.Session.PushRules
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateRoomList fetches the list of rooms the user has joined and sends them to the UI.
|
||||||
func (c *Container) UpdateRoomList() {
|
func (c *Container) UpdateRoomList() {
|
||||||
resp, err := c.client.JoinedRooms()
|
resp, err := c.client.JoinedRooms()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -151,11 +163,13 @@ func (c *Container) UpdateRoomList() {
|
|||||||
c.ui.MainView().SetRooms(resp.JoinedRooms)
|
c.ui.MainView().SetRooms(resp.JoinedRooms)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnLogout stops the syncer and moves the UI back to the login view.
|
||||||
func (c *Container) OnLogout() {
|
func (c *Container) OnLogout() {
|
||||||
c.Stop()
|
c.Stop()
|
||||||
c.ui.SetView(ifc.ViewLogin)
|
c.ui.SetView(ifc.ViewLogin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnLogin initializes the syncer and updates the room list.
|
||||||
func (c *Container) OnLogin() {
|
func (c *Container) OnLogin() {
|
||||||
c.client.Store = c.config.Session
|
c.client.Store = c.config.Session
|
||||||
|
|
||||||
@ -169,6 +183,7 @@ func (c *Container) OnLogin() {
|
|||||||
c.UpdateRoomList()
|
c.UpdateRoomList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start moves the UI to the main view, calls OnLogin() and runs the syncer forever until stopped with Stop()
|
||||||
func (c *Container) Start() {
|
func (c *Container) Start() {
|
||||||
defer c.gmx.Recover()
|
defer c.gmx.Recover()
|
||||||
|
|
||||||
@ -197,6 +212,7 @@ func (c *Container) Start() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NotifyMessage sends a desktop notification of the message with the given details.
|
||||||
func (c *Container) NotifyMessage(room *rooms.Room, sender, text string, critical bool) {
|
func (c *Container) NotifyMessage(room *rooms.Room, sender, text string, critical bool) {
|
||||||
if room.GetTitle() != sender {
|
if room.GetTitle() != sender {
|
||||||
sender = fmt.Sprintf("%s (%s)", sender, room.GetTitle())
|
sender = fmt.Sprintf("%s (%s)", sender, room.GetTitle())
|
||||||
@ -204,11 +220,12 @@ func (c *Container) NotifyMessage(room *rooms.Room, sender, text string, critica
|
|||||||
notification.Send(sender, text, critical)
|
notification.Send(sender, text, critical)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandleMessage is the event handler for the m.room.message timeline event.
|
||||||
func (c *Container) HandleMessage(evt *gomatrix.Event) {
|
func (c *Container) HandleMessage(evt *gomatrix.Event) {
|
||||||
room, message := c.ui.MainView().ProcessMessageEvent(evt)
|
room, message := c.ui.MainView().ProcessMessageEvent(evt)
|
||||||
if room != nil {
|
if room != nil {
|
||||||
pushRules := c.PushRules().GetActions(room.Room, evt).Should()
|
pushRules := c.PushRules().GetActions(room.Room, evt).Should()
|
||||||
if (pushRules.Notify || !pushRules.NotifySpecified) && evt.Sender != c.config.Session.MXID {
|
if (pushRules.Notify || !pushRules.NotifySpecified) && evt.Sender != c.config.Session.UserID {
|
||||||
c.NotifyMessage(room.Room, message.Sender, message.Text, pushRules.Highlight)
|
c.NotifyMessage(room.Room, message.Sender, message.Text, pushRules.Highlight)
|
||||||
}
|
}
|
||||||
if pushRules.Highlight {
|
if pushRules.Highlight {
|
||||||
@ -222,15 +239,17 @@ func (c *Container) HandleMessage(evt *gomatrix.Event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandlePushRules is the event handler for the m.push_rules account data event.
|
||||||
func (c *Container) HandlePushRules(evt *gomatrix.Event) {
|
func (c *Container) HandlePushRules(evt *gomatrix.Event) {
|
||||||
debug.Print("Received updated push rules")
|
debug.Print("Received updated push rules")
|
||||||
var err error
|
var err error
|
||||||
c.config.Session.PushRules, err = gomx_ext.EventToPushRules(evt)
|
c.config.Session.PushRules, err = pushrules.EventToPushRules(evt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Print("Failed to convert event to push rules:", err)
|
debug.Print("Failed to convert event to push rules:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandleMembership is the event handler for the m.room.membership state event.
|
||||||
func (c *Container) HandleMembership(evt *gomatrix.Event) {
|
func (c *Container) HandleMembership(evt *gomatrix.Event) {
|
||||||
const Hour = 1 * 60 * 60 * 1000
|
const Hour = 1 * 60 * 60 * 1000
|
||||||
if evt.Unsigned.Age > Hour {
|
if evt.Unsigned.Age > Hour {
|
||||||
@ -249,6 +268,7 @@ func (c *Container) HandleMembership(evt *gomatrix.Event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandleTyping is the event handler for the m.typing event.
|
||||||
func (c *Container) HandleTyping(evt *gomatrix.Event) {
|
func (c *Container) HandleTyping(evt *gomatrix.Event) {
|
||||||
users := evt.Content["user_ids"].([]interface{})
|
users := evt.Content["user_ids"].([]interface{})
|
||||||
|
|
||||||
@ -259,6 +279,7 @@ func (c *Container) HandleTyping(evt *gomatrix.Event) {
|
|||||||
c.ui.MainView().SetTyping(evt.RoomID, strUsers)
|
c.ui.MainView().SetTyping(evt.RoomID, strUsers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendMessage sends a message with the given text to the given room.
|
||||||
func (c *Container) SendMessage(roomID, text string) (string, error) {
|
func (c *Container) SendMessage(roomID, text string) (string, error) {
|
||||||
defer c.gmx.Recover()
|
defer c.gmx.Recover()
|
||||||
c.SendTyping(roomID, false)
|
c.SendTyping(roomID, false)
|
||||||
@ -269,6 +290,7 @@ func (c *Container) SendMessage(roomID, text string) (string, error) {
|
|||||||
return resp.EventID, nil
|
return resp.EventID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendTyping sets whether or not the user is typing in the given room.
|
||||||
func (c *Container) SendTyping(roomID string, typing bool) {
|
func (c *Container) SendTyping(roomID string, typing bool) {
|
||||||
defer c.gmx.Recover()
|
defer c.gmx.Recover()
|
||||||
ts := time.Now().Unix()
|
ts := time.Now().Unix()
|
||||||
@ -277,14 +299,15 @@ func (c *Container) SendTyping(roomID string, typing bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if typing {
|
if typing {
|
||||||
c.client.UserTyping(roomID, true, 5000)
|
c.client.UserTyping(roomID, true, 20000)
|
||||||
c.typing = ts + 5
|
c.typing = ts + 15
|
||||||
} else {
|
} else {
|
||||||
c.client.UserTyping(roomID, false, 0)
|
c.client.UserTyping(roomID, false, 0)
|
||||||
c.typing = 0
|
c.typing = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JoinRoom makes the current user try to join the given room.
|
||||||
func (c *Container) JoinRoom(roomID string) error {
|
func (c *Container) JoinRoom(roomID string) error {
|
||||||
if len(roomID) == 0 {
|
if len(roomID) == 0 {
|
||||||
return fmt.Errorf("invalid room ID")
|
return fmt.Errorf("invalid room ID")
|
||||||
@ -303,6 +326,7 @@ func (c *Container) JoinRoom(roomID string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LeaveRoom makes the current user leave the given room.
|
||||||
func (c *Container) LeaveRoom(roomID string) error {
|
func (c *Container) LeaveRoom(roomID string) error {
|
||||||
if len(roomID) == 0 {
|
if len(roomID) == 0 {
|
||||||
return fmt.Errorf("invalid room ID")
|
return fmt.Errorf("invalid room ID")
|
||||||
@ -316,6 +340,7 @@ func (c *Container) LeaveRoom(roomID string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getState requests the state of the given room.
|
||||||
func (c *Container) getState(roomID string) []*gomatrix.Event {
|
func (c *Container) getState(roomID string) []*gomatrix.Event {
|
||||||
content := make([]*gomatrix.Event, 0)
|
content := make([]*gomatrix.Event, 0)
|
||||||
err := c.client.StateEvent(roomID, "", "", &content)
|
err := c.client.StateEvent(roomID, "", "", &content)
|
||||||
@ -326,6 +351,7 @@ func (c *Container) getState(roomID string) []*gomatrix.Event {
|
|||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetHistory fetches room history.
|
||||||
func (c *Container) GetHistory(roomID, prevBatch string, limit int) ([]gomatrix.Event, string, error) {
|
func (c *Container) GetHistory(roomID, prevBatch string, limit int) ([]gomatrix.Event, string, error) {
|
||||||
resp, err := c.client.Messages(roomID, prevBatch, "", 'b', limit)
|
resp, err := c.client.Messages(roomID, prevBatch, "", 'b', limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -334,6 +360,9 @@ func (c *Container) GetHistory(roomID, prevBatch string, limit int) ([]gomatrix.
|
|||||||
return resp.Chunk, resp.End, nil
|
return resp.Chunk, resp.End, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRoom gets the room instance stored in the session.
|
||||||
|
//
|
||||||
|
// If the room doesn't have any state events stored, its state will be updated.
|
||||||
func (c *Container) GetRoom(roomID string) *rooms.Room {
|
func (c *Container) GetRoom(roomID string) *rooms.Room {
|
||||||
room := c.config.Session.GetRoom(roomID)
|
room := c.config.Session.GetRoom(roomID)
|
||||||
if room != nil && len(room.State) == 0 {
|
if room != nil && len(room.State) == 0 {
|
||||||
|
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
|
||||||
|
}
|
@ -20,18 +20,34 @@ import (
|
|||||||
"maunium.net/go/gomatrix"
|
"maunium.net/go/gomatrix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Membership string
|
||||||
|
|
||||||
|
// The allowed membership states as specified in spec section 10.5.5.
|
||||||
|
const (
|
||||||
|
MembershipJoin Membership = "join"
|
||||||
|
MembershipLeave Membership = "leave"
|
||||||
|
MembershipInvite Membership = "invite"
|
||||||
|
MembershipKnock Membership = "knock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Member represents a member in a room.
|
||||||
type Member struct {
|
type Member struct {
|
||||||
UserID string `json:"-"`
|
// The MXID of the member.
|
||||||
Membership string `json:"membership"`
|
UserID string `json:"-"`
|
||||||
DisplayName string `json:"displayname"`
|
// The membership status. Defaults to leave.
|
||||||
AvatarURL string `json:"avatar_url"`
|
Membership Membership `json:"membership"`
|
||||||
|
// The display name of the user. Defaults to the user ID.
|
||||||
|
DisplayName string `json:"displayname"`
|
||||||
|
// The avatar URL of the user. Defaults to an empty string.
|
||||||
|
AvatarURL string `json:"avatar_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eventToRoomMember converts a m.room.member state event into a Member object.
|
||||||
func eventToRoomMember(userID string, event *gomatrix.Event) *Member {
|
func eventToRoomMember(userID string, event *gomatrix.Event) *Member {
|
||||||
if event == nil {
|
if event == nil {
|
||||||
return &Member{
|
return &Member{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
Membership: "leave",
|
Membership: MembershipLeave,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
membership, _ := event.Content["membership"].(string)
|
membership, _ := event.Content["membership"].(string)
|
||||||
@ -44,7 +60,7 @@ func eventToRoomMember(userID string, event *gomatrix.Event) *Member {
|
|||||||
|
|
||||||
return &Member{
|
return &Member{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
Membership: membership,
|
Membership: Membership(membership),
|
||||||
DisplayName: displayName,
|
DisplayName: displayName,
|
||||||
AvatarURL: avatarURL,
|
AvatarURL: avatarURL,
|
||||||
}
|
}
|
||||||
|
@ -26,11 +26,18 @@ import (
|
|||||||
type Room struct {
|
type Room struct {
|
||||||
*gomatrix.Room
|
*gomatrix.Room
|
||||||
|
|
||||||
|
// The first batch of events that has been fetched for this room.
|
||||||
|
// Used for fetching additional history.
|
||||||
PrevBatch string
|
PrevBatch string
|
||||||
Owner string
|
// The MXID of the user whose session this room was created for.
|
||||||
|
SessionUserID string
|
||||||
|
// MXID -> Member cache calculated from membership events.
|
||||||
memberCache map[string]*Member
|
memberCache map[string]*Member
|
||||||
|
// The first non-SessionUserID member in the room. Calculated at the same time as memberCache.
|
||||||
firstMemberCache string
|
firstMemberCache string
|
||||||
|
// The name of the room. Calculated from the state event name, canonical_alias or alias or the member cache.
|
||||||
nameCache string
|
nameCache string
|
||||||
|
// The topic of the room. Directly fetched from the m.room.topic state event.
|
||||||
topicCache string
|
topicCache string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,7 +142,7 @@ func (room *Room) updateNameFromMembers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// updateNameCache updates the room display name based on the room state in the order
|
// updateNameCache updates the room display name based on the room state in the order
|
||||||
// specified in section 11.2.2.5 of r0.3.0 of the Client-Server API specification.
|
// specified in spec section 11.2.2.5.
|
||||||
func (room *Room) updateNameCache() {
|
func (room *Room) updateNameCache() {
|
||||||
if len(room.nameCache) == 0 {
|
if len(room.nameCache) == 0 {
|
||||||
room.updateNameFromNameEvent()
|
room.updateNameFromNameEvent()
|
||||||
@ -167,7 +174,7 @@ func (room *Room) createMemberCache() map[string]*Member {
|
|||||||
room.firstMemberCache = ""
|
room.firstMemberCache = ""
|
||||||
if events != nil {
|
if events != nil {
|
||||||
for userID, event := range events {
|
for userID, event := range events {
|
||||||
if len(room.firstMemberCache) == 0 && userID != room.Owner {
|
if len(room.firstMemberCache) == 0 && userID != room.SessionUserID {
|
||||||
room.firstMemberCache = userID
|
room.firstMemberCache = userID
|
||||||
}
|
}
|
||||||
member := eventToRoomMember(userID, event)
|
member := eventToRoomMember(userID, event)
|
||||||
@ -204,7 +211,7 @@ func (room *Room) GetMember(userID string) *Member {
|
|||||||
// NewRoom creates a new Room with the given ID
|
// NewRoom creates a new Room with the given ID
|
||||||
func NewRoom(roomID, owner string) *Room {
|
func NewRoom(roomID, owner string) *Room {
|
||||||
return &Room{
|
return &Room{
|
||||||
Room: gomatrix.NewRoom(roomID),
|
Room: gomatrix.NewRoom(roomID),
|
||||||
Owner: owner,
|
SessionUserID: owner,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,21 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
// Based on https://github.com/matrix-org/gomatrix/blob/master/sync.go
|
||||||
|
|
||||||
package matrix
|
package matrix
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -26,15 +44,16 @@ func NewGomuksSyncer(session *config.Session) *GomuksSyncer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProcessResponse processes a Matrix sync response.
|
||||||
func (s *GomuksSyncer) ProcessResponse(res *gomatrix.RespSync, since string) (err error) {
|
func (s *GomuksSyncer) ProcessResponse(res *gomatrix.RespSync, since string) (err error) {
|
||||||
if !s.shouldProcessResponse(res, since) {
|
if len(since) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// gdebug.Print("Processing sync response", since, res)
|
// gdebug.Print("Processing sync response", since, res)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
err = fmt.Errorf("ProcessResponse panicked! userID=%s since=%s panic=%s\n%s", s.Session.MXID, since, r, debug.Stack())
|
err = fmt.Errorf("ProcessResponse panicked! userID=%s since=%s panic=%s\n%s", s.Session.UserID, since, r, debug.Stack())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -99,15 +118,6 @@ func (s *GomuksSyncer) OnEventType(eventType string, callback gomatrix.OnEventLi
|
|||||||
s.listeners[eventType] = append(s.listeners[eventType], callback)
|
s.listeners[eventType] = append(s.listeners[eventType], callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
// shouldProcessResponse returns true if the response should be processed. May modify the response to remove
|
|
||||||
// stuff that shouldn't be processed.
|
|
||||||
func (s *GomuksSyncer) shouldProcessResponse(resp *gomatrix.RespSync, since string) bool {
|
|
||||||
if since == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *GomuksSyncer) notifyListeners(event *gomatrix.Event) {
|
func (s *GomuksSyncer) notifyListeners(event *gomatrix.Event) {
|
||||||
listeners, exists := s.listeners[event.Type]
|
listeners, exists := s.listeners[event.Type]
|
||||||
if !exists {
|
if !exists {
|
||||||
|
@ -29,7 +29,7 @@ func (ui *GomuksUI) NewLoginView() tview.Primitive {
|
|||||||
}
|
}
|
||||||
|
|
||||||
homeserver := widget.NewAdvancedInputField().SetLabel("Homeserver").SetText(hs).SetFieldWidth(30)
|
homeserver := widget.NewAdvancedInputField().SetLabel("Homeserver").SetText(hs).SetFieldWidth(30)
|
||||||
username := widget.NewAdvancedInputField().SetLabel("Username").SetText(ui.gmx.Config().MXID).SetFieldWidth(30)
|
username := widget.NewAdvancedInputField().SetLabel("Username").SetText(ui.gmx.Config().UserID).SetFieldWidth(30)
|
||||||
password := widget.NewAdvancedInputField().SetLabel("Password").SetMaskCharacter('*').SetFieldWidth(30)
|
password := widget.NewAdvancedInputField().SetLabel("Password").SetMaskCharacter('*').SetFieldWidth(30)
|
||||||
|
|
||||||
ui.loginView = tview.NewForm()
|
ui.loginView = tview.NewForm()
|
||||||
|
@ -418,7 +418,7 @@ func (view *MainView) processOwnMembershipChange(evt *gomatrix.Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) ProcessMembershipEvent(evt *gomatrix.Event, new bool) (room *widget.RoomView, message *types.Message) {
|
func (view *MainView) ProcessMembershipEvent(evt *gomatrix.Event, new bool) (room *widget.RoomView, message *types.Message) {
|
||||||
if new && evt.StateKey != nil && *evt.StateKey == view.config.Session.MXID {
|
if new && evt.StateKey != nil && *evt.StateKey == view.config.Session.UserID {
|
||||||
view.processOwnMembershipChange(evt)
|
view.processOwnMembershipChange(evt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user