Update stuff and move pushrules to mautrix-go
This commit is contained in:
parent
ff20c2c44f
commit
815190be14
@ -26,9 +26,10 @@ import (
|
|||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
|
"maunium.net/go/mautrix/pushrules"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/debug"
|
"maunium.net/go/gomuks/debug"
|
||||||
"maunium.net/go/gomuks/matrix/pushrules"
|
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -52,9 +53,9 @@ type UserPreferences struct {
|
|||||||
|
|
||||||
// Config contains the main config of gomuks.
|
// Config contains the main config of gomuks.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
UserID string `yaml:"mxid"`
|
UserID id.UserID `yaml:"mxid"`
|
||||||
AccessToken string `yaml:"access_token"`
|
AccessToken string `yaml:"access_token"`
|
||||||
HS string `yaml:"homeserver"`
|
HS string `yaml:"homeserver"`
|
||||||
|
|
||||||
RoomCacheSize int `yaml:"room_cache_size"`
|
RoomCacheSize int `yaml:"room_cache_size"`
|
||||||
RoomCacheAge int64 `yaml:"room_cache_age"`
|
RoomCacheAge int64 `yaml:"room_cache_age"`
|
||||||
@ -242,36 +243,36 @@ func (config *Config) save(name, dir, file string, source interface{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (config *Config) GetUserID() string {
|
func (config *Config) GetUserID() id.UserID {
|
||||||
return config.UserID
|
return config.UserID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (config *Config) SaveFilterID(_, filterID string) {
|
func (config *Config) SaveFilterID(_ id.UserID, filterID string) {
|
||||||
config.AuthCache.FilterID = filterID
|
config.AuthCache.FilterID = filterID
|
||||||
config.SaveAuthCache()
|
config.SaveAuthCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (config *Config) LoadFilterID(_ string) string {
|
func (config *Config) LoadFilterID(_ id.UserID) string {
|
||||||
return config.AuthCache.FilterID
|
return config.AuthCache.FilterID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (config *Config) SaveNextBatch(_, nextBatch string) {
|
func (config *Config) SaveNextBatch(_ id.UserID, nextBatch string) {
|
||||||
config.AuthCache.NextBatch = nextBatch
|
config.AuthCache.NextBatch = nextBatch
|
||||||
config.SaveAuthCache()
|
config.SaveAuthCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (config *Config) LoadNextBatch(_ string) string {
|
func (config *Config) LoadNextBatch(_ id.UserID) string {
|
||||||
return config.AuthCache.NextBatch
|
return config.AuthCache.NextBatch
|
||||||
}
|
}
|
||||||
|
|
||||||
func (config *Config) SaveRoom(room *mautrix.Room) {
|
func (config *Config) SaveRoom(_ *mautrix.Room) {
|
||||||
panic("SaveRoom is not supported")
|
panic("SaveRoom is not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (config *Config) LoadRoom(roomID string) *mautrix.Room {
|
func (config *Config) LoadRoom(_ id.RoomID) *mautrix.Room {
|
||||||
panic("LoadRoom is not supported")
|
panic("LoadRoom is not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (config *Config) GetRoom(roomID string) *rooms.Room {
|
func (config *Config) GetRoom(roomID id.RoomID) *rooms.Room {
|
||||||
return config.Rooms.GetOrCreate(roomID)
|
return config.Rooms.GetOrCreate(roomID)
|
||||||
}
|
}
|
||||||
|
@ -1,149 +0,0 @@
|
|||||||
// gomuks - A terminal Matrix client written in Go.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package config_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"maunium.net/go/gomuks/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewConfig_Defaults(t *testing.T) {
|
|
||||||
cfg := config.NewConfig("/tmp/gomuks-test-0", "/tmp/gomuks-test-0")
|
|
||||||
assert.Equal(t, "/tmp/gomuks-test-0", cfg.Dir)
|
|
||||||
assert.Equal(t, "/tmp/gomuks-test-0/history.db", cfg.HistoryPath)
|
|
||||||
assert.Equal(t, "/tmp/gomuks-test-0/media", cfg.MediaDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfig_Load_NonexistentDoesntFail(t *testing.T) {
|
|
||||||
cfg := config.NewConfig("/tmp/gomuks-test-1", "/tmp/gomuks-test-1")
|
|
||||||
|
|
||||||
defer os.RemoveAll("/tmp/gomuks-test-1")
|
|
||||||
|
|
||||||
cfg.Load()
|
|
||||||
|
|
||||||
stat, err := os.Stat(cfg.MediaDir)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.True(t, stat.IsDir())
|
|
||||||
|
|
||||||
/* FIXME
|
|
||||||
stat, err = os.Stat(cfg.HistoryDir)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.True(t, stat.IsDir())*/
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfig_Load_DirectoryFails(t *testing.T) {
|
|
||||||
os.MkdirAll("/tmp/gomuks-test-2/config.yaml", 0700)
|
|
||||||
cfg := config.NewConfig("/tmp/gomuks-test-2", "/tmp/gomuks-test-2")
|
|
||||||
|
|
||||||
defer os.RemoveAll("/tmp/gomuks-test-2")
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err == nil {
|
|
||||||
t.Fatalf("Load() didn't panic")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
cfg.Load()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfig_Load_ExistingFileIsLoaded(t *testing.T) {
|
|
||||||
os.MkdirAll("/tmp/gomuks-test-3", 0700)
|
|
||||||
ioutil.WriteFile("/tmp/gomuks-test-3/config.yaml", []byte(`{
|
|
||||||
"mxid": "foo",
|
|
||||||
"homeserver": "bar",
|
|
||||||
"history_path": "/tmp/gomuks-test-3/foo.db",
|
|
||||||
"media_dir": "/tmp/gomuks-test-3/bar"
|
|
||||||
}`), 0700)
|
|
||||||
cfg := config.NewConfig("/tmp/gomuks-test-3", "/tmp/gomuks-test-3")
|
|
||||||
|
|
||||||
defer os.RemoveAll("/tmp/gomuks-test-3")
|
|
||||||
|
|
||||||
cfg.Load()
|
|
||||||
|
|
||||||
assert.Equal(t, "foo", cfg.UserID)
|
|
||||||
assert.Equal(t, "bar", cfg.HS)
|
|
||||||
assert.Equal(t, "/tmp/gomuks-test-3/foo.db", cfg.HistoryPath)
|
|
||||||
assert.Equal(t, "/tmp/gomuks-test-3/bar", cfg.MediaDir)
|
|
||||||
|
|
||||||
stat, err := os.Stat(cfg.MediaDir)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.True(t, stat.IsDir())
|
|
||||||
|
|
||||||
/* FIXME
|
|
||||||
stat, err = os.Stat(cfg.HistoryDir)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.True(t, stat.IsDir())*/
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfig_Load_InvalidExistingFilePanics(t *testing.T) {
|
|
||||||
os.MkdirAll("/tmp/gomuks-test-4", 0700)
|
|
||||||
ioutil.WriteFile("/tmp/gomuks-test-4/config.yaml", []byte(`this is not JSON.`), 0700)
|
|
||||||
cfg := config.NewConfig("/tmp/gomuks-test-4", "/tmp/gomuks-test-4")
|
|
||||||
|
|
||||||
defer os.RemoveAll("/tmp/gomuks-test-4")
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err == nil {
|
|
||||||
t.Fatalf("Load() didn't panic")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
cfg.Load()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfig_Clear(t *testing.T) {
|
|
||||||
cfg := config.NewConfig("/tmp/gomuks-test-5", "/tmp/gomuks-test-5")
|
|
||||||
|
|
||||||
defer os.RemoveAll("/tmp/gomuks-test-5")
|
|
||||||
|
|
||||||
cfg.Load()
|
|
||||||
|
|
||||||
stat, err := os.Stat(cfg.MediaDir)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.True(t, stat.IsDir())
|
|
||||||
|
|
||||||
/* FIXME
|
|
||||||
stat, err = os.Stat(cfg.HistoryDir)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.True(t, stat.IsDir())*/
|
|
||||||
|
|
||||||
cfg.Clear()
|
|
||||||
|
|
||||||
stat, err = os.Stat(cfg.MediaDir)
|
|
||||||
assert.True(t, os.IsNotExist(err))
|
|
||||||
assert.Nil(t, stat)
|
|
||||||
|
|
||||||
/* FIXME
|
|
||||||
stat, err = os.Stat(cfg.HistoryDir)
|
|
||||||
assert.True(t, os.IsNotExist(err))
|
|
||||||
assert.Nil(t, stat)*/
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfig_Save(t *testing.T) {
|
|
||||||
cfg := config.NewConfig("/tmp/gomuks-test-6", "/tmp/gomuks-test-6")
|
|
||||||
|
|
||||||
defer os.RemoveAll("/tmp/gomuks-test-6")
|
|
||||||
|
|
||||||
cfg.Load()
|
|
||||||
cfg.Save()
|
|
||||||
|
|
||||||
dat, err := ioutil.ReadFile("/tmp/gomuks-test-6/config.yaml")
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Contains(t, string(dat), "/tmp/gomuks-test-6")
|
|
||||||
}
|
|
@ -17,16 +17,18 @@
|
|||||||
package ifc
|
package ifc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"maunium.net/go/gomuks/config"
|
|
||||||
"maunium.net/go/gomuks/matrix/event"
|
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
|
"maunium.net/go/mautrix/event"
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
|
|
||||||
|
"maunium.net/go/gomuks/config"
|
||||||
|
"maunium.net/go/gomuks/matrix/muksevt"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Relation struct {
|
type Relation struct {
|
||||||
Type mautrix.RelationType
|
Type event.RelationType
|
||||||
Event *event.Event
|
Event *muksevt.Event
|
||||||
}
|
}
|
||||||
|
|
||||||
type MatrixContainer interface {
|
type MatrixContainer interface {
|
||||||
@ -42,23 +44,23 @@ type MatrixContainer interface {
|
|||||||
Logout()
|
Logout()
|
||||||
|
|
||||||
SendPreferencesToMatrix()
|
SendPreferencesToMatrix()
|
||||||
PrepareMarkdownMessage(roomID string, msgtype mautrix.MessageType, text, html string, relation *Relation) *event.Event
|
PrepareMarkdownMessage(roomID id.RoomID, msgtype event.MessageType, text, html string, relation *Relation) *muksevt.Event
|
||||||
SendEvent(evt *event.Event) (string, error)
|
SendEvent(evt *muksevt.Event) (id.EventID, error)
|
||||||
Redact(roomID, eventID, reason string) error
|
Redact(roomID id.RoomID, eventID id.EventID, reason string) error
|
||||||
SendTyping(roomID string, typing bool)
|
SendTyping(roomID id.RoomID, typing bool)
|
||||||
MarkRead(roomID, eventID string)
|
MarkRead(roomID id.RoomID, eventID id.EventID)
|
||||||
JoinRoom(roomID, server string) (*rooms.Room, error)
|
JoinRoom(roomID id.RoomID, server string) (*rooms.Room, error)
|
||||||
LeaveRoom(roomID string) error
|
LeaveRoom(roomID id.RoomID) error
|
||||||
CreateRoom(req *mautrix.ReqCreateRoom) (*rooms.Room, error)
|
CreateRoom(req *mautrix.ReqCreateRoom) (*rooms.Room, error)
|
||||||
|
|
||||||
FetchMembers(room *rooms.Room) error
|
FetchMembers(room *rooms.Room) error
|
||||||
GetHistory(room *rooms.Room, limit int) ([]*event.Event, error)
|
GetHistory(room *rooms.Room, limit int) ([]*muksevt.Event, error)
|
||||||
GetEvent(room *rooms.Room, eventID string) (*event.Event, error)
|
GetEvent(room *rooms.Room, eventID id.EventID) (*muksevt.Event, error)
|
||||||
GetRoom(roomID string) *rooms.Room
|
GetRoom(roomID id.RoomID) *rooms.Room
|
||||||
GetOrCreateRoom(roomID string) *rooms.Room
|
GetOrCreateRoom(roomID id.RoomID) *rooms.Room
|
||||||
|
|
||||||
Download(uri mautrix.ContentURI) ([]byte, error)
|
Download(uri id.ContentURI) ([]byte, error)
|
||||||
DownloadToDisk(uri mautrix.ContentURI, target string) (string, error)
|
DownloadToDisk(uri id.ContentURI, target string) (string, error)
|
||||||
GetDownloadURL(uri mautrix.ContentURI) string
|
GetDownloadURL(uri id.ContentURI) string
|
||||||
GetCachePath(uri mautrix.ContentURI) string
|
GetCachePath(uri id.ContentURI) string
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,10 @@ package ifc
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/matrix/event"
|
"maunium.net/go/gomuks/matrix/muksevt"
|
||||||
"maunium.net/go/gomuks/matrix/pushrules"
|
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
|
"maunium.net/go/mautrix/pushrules"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UIProvider func(gmx Gomuks) GomuksUI
|
type UIProvider func(gmx Gomuks) GomuksUI
|
||||||
@ -40,7 +41,7 @@ type GomuksUI interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MainView interface {
|
type MainView interface {
|
||||||
GetRoom(roomID string) RoomView
|
GetRoom(roomID id.RoomID) RoomView
|
||||||
AddRoom(room *rooms.Room)
|
AddRoom(room *rooms.Room)
|
||||||
RemoveRoom(room *rooms.Room)
|
RemoveRoom(room *rooms.Room)
|
||||||
SetRooms(rooms *rooms.RoomCache)
|
SetRooms(rooms *rooms.RoomCache)
|
||||||
@ -48,7 +49,7 @@ type MainView interface {
|
|||||||
|
|
||||||
UpdateTags(room *rooms.Room)
|
UpdateTags(room *rooms.Room)
|
||||||
|
|
||||||
SetTyping(roomID string, users []string)
|
SetTyping(roomID id.RoomID, users []id.UserID)
|
||||||
|
|
||||||
NotifyMessage(room *rooms.Room, message Message, should pushrules.PushActionArrayShould)
|
NotifyMessage(room *rooms.Room, message Message, should pushrules.PushActionArrayShould)
|
||||||
}
|
}
|
||||||
@ -57,23 +58,23 @@ type RoomView interface {
|
|||||||
MxRoom() *rooms.Room
|
MxRoom() *rooms.Room
|
||||||
|
|
||||||
SetCompletions(completions []string)
|
SetCompletions(completions []string)
|
||||||
SetTyping(users []string)
|
SetTyping(users []id.UserID)
|
||||||
UpdateUserList()
|
UpdateUserList()
|
||||||
|
|
||||||
AddEvent(evt *event.Event) Message
|
AddEvent(evt *muksevt.Event) Message
|
||||||
AddRedaction(evt *event.Event)
|
AddRedaction(evt *muksevt.Event)
|
||||||
AddEdit(evt *event.Event)
|
AddEdit(evt *muksevt.Event)
|
||||||
AddReaction(evt *event.Event, key string)
|
AddReaction(evt *muksevt.Event, key string)
|
||||||
GetEvent(eventID string) Message
|
GetEvent(eventID id.EventID) Message
|
||||||
AddServiceMessage(message string)
|
AddServiceMessage(message string)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Message interface {
|
type Message interface {
|
||||||
ID() string
|
ID() id.EventID
|
||||||
Time() time.Time
|
Time() time.Time
|
||||||
NotificationSenderName() string
|
NotificationSenderName() string
|
||||||
NotificationContent() string
|
NotificationContent() string
|
||||||
|
|
||||||
SetIsHighlight(highlight bool)
|
SetIsHighlight(highlight bool)
|
||||||
SetID(id string)
|
SetID(id id.EventID)
|
||||||
}
|
}
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
Glob is licensed under the MIT "Expat" License:
|
|
||||||
|
|
||||||
Copyright (c) 2016: Zachary Yedidia.
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
||||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
||||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -1,28 +0,0 @@
|
|||||||
# String globbing in Go
|
|
||||||
|
|
||||||
[![GoDoc](https://godoc.org/github.com/zyedidia/glob?status.svg)](http://godoc.org/github.com/zyedidia/glob)
|
|
||||||
|
|
||||||
This package adds support for globs in Go.
|
|
||||||
|
|
||||||
It simply converts glob expressions to regexps. I try to follow the standard defined [here](http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_13).
|
|
||||||
|
|
||||||
# Example
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "github.com/zyedidia/glob"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
glob, err := glob.Compile("{*.go,*.c}")
|
|
||||||
if err != nil {
|
|
||||||
// Error
|
|
||||||
}
|
|
||||||
|
|
||||||
glob.Match([]byte("test.c")) // true
|
|
||||||
glob.Match([]byte("hello.go")) // true
|
|
||||||
glob.Match([]byte("test.d")) // false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
You can call all the same functions on a glob that you can call on a regexp.
|
|
108
lib/glob/glob.go
108
lib/glob/glob.go
@ -1,108 +0,0 @@
|
|||||||
// Package glob provides objects for matching strings with globs
|
|
||||||
package glob
|
|
||||||
|
|
||||||
import "regexp"
|
|
||||||
|
|
||||||
// Glob is a wrapper of *regexp.Regexp.
|
|
||||||
// It should contain a glob expression compiled into a regular expression.
|
|
||||||
type Glob struct {
|
|
||||||
*regexp.Regexp
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile a takes a glob expression as a string and transforms it
|
|
||||||
// into a *Glob object (which is really just a regular expression)
|
|
||||||
// Compile also returns a possible error.
|
|
||||||
func Compile(pattern string) (*Glob, error) {
|
|
||||||
r, err := globToRegex(pattern)
|
|
||||||
return &Glob{r}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func globToRegex(glob string) (*regexp.Regexp, error) {
|
|
||||||
regex := ""
|
|
||||||
inGroup := 0
|
|
||||||
inClass := 0
|
|
||||||
firstIndexInClass := -1
|
|
||||||
arr := []byte(glob)
|
|
||||||
|
|
||||||
hasGlobCharacters := false
|
|
||||||
|
|
||||||
for i := 0; i < len(arr); i++ {
|
|
||||||
ch := arr[i]
|
|
||||||
|
|
||||||
switch ch {
|
|
||||||
case '\\':
|
|
||||||
i++
|
|
||||||
if i >= len(arr) {
|
|
||||||
regex += "\\"
|
|
||||||
} else {
|
|
||||||
next := arr[i]
|
|
||||||
switch next {
|
|
||||||
case ',':
|
|
||||||
// Nothing
|
|
||||||
case 'Q', 'E':
|
|
||||||
regex += "\\\\"
|
|
||||||
default:
|
|
||||||
regex += "\\"
|
|
||||||
}
|
|
||||||
regex += string(next)
|
|
||||||
}
|
|
||||||
case '*':
|
|
||||||
if inClass == 0 {
|
|
||||||
regex += ".*"
|
|
||||||
} else {
|
|
||||||
regex += "*"
|
|
||||||
}
|
|
||||||
hasGlobCharacters = true
|
|
||||||
case '?':
|
|
||||||
if inClass == 0 {
|
|
||||||
regex += "."
|
|
||||||
} else {
|
|
||||||
regex += "?"
|
|
||||||
}
|
|
||||||
hasGlobCharacters = true
|
|
||||||
case '[':
|
|
||||||
inClass++
|
|
||||||
firstIndexInClass = i + 1
|
|
||||||
regex += "["
|
|
||||||
hasGlobCharacters = true
|
|
||||||
case ']':
|
|
||||||
inClass--
|
|
||||||
regex += "]"
|
|
||||||
case '.', '(', ')', '+', '|', '^', '$', '@', '%':
|
|
||||||
if inClass == 0 || (firstIndexInClass == i && ch == '^') {
|
|
||||||
regex += "\\"
|
|
||||||
}
|
|
||||||
regex += string(ch)
|
|
||||||
hasGlobCharacters = true
|
|
||||||
case '!':
|
|
||||||
if firstIndexInClass == i {
|
|
||||||
regex += "^"
|
|
||||||
} else {
|
|
||||||
regex += "!"
|
|
||||||
}
|
|
||||||
hasGlobCharacters = true
|
|
||||||
case '{':
|
|
||||||
inGroup++
|
|
||||||
regex += "("
|
|
||||||
hasGlobCharacters = true
|
|
||||||
case '}':
|
|
||||||
inGroup--
|
|
||||||
regex += ")"
|
|
||||||
case ',':
|
|
||||||
if inGroup > 0 {
|
|
||||||
regex += "|"
|
|
||||||
hasGlobCharacters = true
|
|
||||||
} else {
|
|
||||||
regex += ","
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
regex += string(ch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasGlobCharacters {
|
|
||||||
return regexp.Compile("^" + regex + "$")
|
|
||||||
} else {
|
|
||||||
return regexp.Compile(regex)
|
|
||||||
}
|
|
||||||
}
|
|
@ -26,9 +26,10 @@ import (
|
|||||||
sync "github.com/sasha-s/go-deadlock"
|
sync "github.com/sasha-s/go-deadlock"
|
||||||
bolt "go.etcd.io/bbolt"
|
bolt "go.etcd.io/bbolt"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/matrix/event"
|
"maunium.net/go/gomuks/matrix/muksevt"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix/event"
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HistoryManager struct {
|
type HistoryManager struct {
|
||||||
@ -87,7 +88,7 @@ func (hm *HistoryManager) Close() error {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
EventNotFoundError = errors.New("event not found")
|
EventNotFoundError = errors.New("event not found")
|
||||||
RoomNotFoundError = errors.New("room not found")
|
RoomNotFoundError = errors.New("room not found")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (hm *HistoryManager) getStreamIndex(tx *bolt.Tx, roomID []byte, eventID []byte) (*bolt.Bucket, []byte, error) {
|
func (hm *HistoryManager) getStreamIndex(tx *bolt.Tx, roomID []byte, eventID []byte) (*bolt.Bucket, []byte, error) {
|
||||||
@ -103,7 +104,7 @@ func (hm *HistoryManager) getStreamIndex(tx *bolt.Tx, roomID []byte, eventID []b
|
|||||||
return stream, index, nil
|
return stream, index, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hm *HistoryManager) getEvent(tx *bolt.Tx, stream *bolt.Bucket, index []byte) (*event.Event, error) {
|
func (hm *HistoryManager) getEvent(tx *bolt.Tx, stream *bolt.Bucket, index []byte) (*muksevt.Event, error) {
|
||||||
eventData := stream.Get(index)
|
eventData := stream.Get(index)
|
||||||
if eventData == nil || len(eventData) == 0 {
|
if eventData == nil || len(eventData) == 0 {
|
||||||
return nil, EventNotFoundError
|
return nil, EventNotFoundError
|
||||||
@ -111,7 +112,7 @@ func (hm *HistoryManager) getEvent(tx *bolt.Tx, stream *bolt.Bucket, index []byt
|
|||||||
return unmarshalEvent(eventData)
|
return unmarshalEvent(eventData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hm *HistoryManager) Get(room *rooms.Room, eventID string) (evt *event.Event, err error) {
|
func (hm *HistoryManager) Get(room *rooms.Room, eventID id.EventID) (evt *muksevt.Event, err error) {
|
||||||
err = hm.db.View(func(tx *bolt.Tx) error {
|
err = hm.db.View(func(tx *bolt.Tx) error {
|
||||||
if stream, index, err := hm.getStreamIndex(tx, []byte(room.ID), []byte(eventID)); err != nil {
|
if stream, index, err := hm.getStreamIndex(tx, []byte(room.ID), []byte(eventID)); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -123,7 +124,7 @@ func (hm *HistoryManager) Get(room *rooms.Room, eventID string) (evt *event.Even
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hm *HistoryManager) Update(room *rooms.Room, eventID string, update func(evt *event.Event) error) error {
|
func (hm *HistoryManager) Update(room *rooms.Room, eventID id.EventID, update func(evt *muksevt.Event) error) error {
|
||||||
return hm.db.Update(func(tx *bolt.Tx) error {
|
return hm.db.Update(func(tx *bolt.Tx) error {
|
||||||
if stream, index, err := hm.getStreamIndex(tx, []byte(room.ID), []byte(eventID)); err != nil {
|
if stream, index, err := hm.getStreamIndex(tx, []byte(room.ID), []byte(eventID)); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -140,18 +141,18 @@ func (hm *HistoryManager) Update(room *rooms.Room, eventID string, update func(e
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hm *HistoryManager) Append(room *rooms.Room, events []*mautrix.Event) ([]*event.Event, error) {
|
func (hm *HistoryManager) Append(room *rooms.Room, events []*event.Event) ([]*muksevt.Event, error) {
|
||||||
return hm.store(room, events, true)
|
return hm.store(room, events, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hm *HistoryManager) Prepend(room *rooms.Room, events []*mautrix.Event) ([]*event.Event, error) {
|
func (hm *HistoryManager) Prepend(room *rooms.Room, events []*event.Event) ([]*muksevt.Event, error) {
|
||||||
return hm.store(room, events, false)
|
return hm.store(room, events, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hm *HistoryManager) store(room *rooms.Room, events []*mautrix.Event, append bool) ([]*event.Event, error) {
|
func (hm *HistoryManager) store(room *rooms.Room, events []*event.Event, append bool) ([]*muksevt.Event, error) {
|
||||||
hm.Lock()
|
hm.Lock()
|
||||||
defer hm.Unlock()
|
defer hm.Unlock()
|
||||||
newEvents := make([]*event.Event, len(events))
|
newEvents := make([]*muksevt.Event, len(events))
|
||||||
err := hm.db.Update(func(tx *bolt.Tx) error {
|
err := hm.db.Update(func(tx *bolt.Tx) error {
|
||||||
streamPointers := tx.Bucket(bucketStreamPointers)
|
streamPointers := tx.Bucket(bucketStreamPointers)
|
||||||
rid := []byte(room.ID)
|
rid := []byte(room.ID)
|
||||||
@ -177,7 +178,7 @@ func (hm *HistoryManager) store(room *rooms.Room, events []*mautrix.Event, appen
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for i, evt := range events {
|
for i, evt := range events {
|
||||||
newEvents[i] = event.Wrap(evt)
|
newEvents[i] = muksevt.Wrap(evt)
|
||||||
if err := put(stream, eventIDs, newEvents[i], ptrStart+uint64(i)); err != nil {
|
if err := put(stream, eventIDs, newEvents[i], ptrStart+uint64(i)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -198,7 +199,7 @@ func (hm *HistoryManager) store(room *rooms.Room, events []*mautrix.Event, appen
|
|||||||
}
|
}
|
||||||
eventCount := uint64(len(events))
|
eventCount := uint64(len(events))
|
||||||
for i, evt := range events {
|
for i, evt := range events {
|
||||||
newEvents[i] = event.Wrap(evt)
|
newEvents[i] = muksevt.Wrap(evt)
|
||||||
if err := put(stream, eventIDs, newEvents[i], -ptrStart-uint64(i)); err != nil {
|
if err := put(stream, eventIDs, newEvents[i], -ptrStart-uint64(i)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -215,12 +216,11 @@ func (hm *HistoryManager) store(room *rooms.Room, events []*mautrix.Event, appen
|
|||||||
return newEvents, err
|
return newEvents, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hm *HistoryManager) Load(room *rooms.Room, num int) (events []*event.Event, err error) {
|
func (hm *HistoryManager) Load(room *rooms.Room, num int) (events []*muksevt.Event, err error) {
|
||||||
hm.Lock()
|
hm.Lock()
|
||||||
defer hm.Unlock()
|
defer hm.Unlock()
|
||||||
err = hm.db.View(func(tx *bolt.Tx) error {
|
err = hm.db.View(func(tx *bolt.Tx) error {
|
||||||
rid := []byte(room.ID)
|
stream := tx.Bucket(bucketRoomStreams).Bucket([]byte(room.ID))
|
||||||
stream := tx.Bucket(bucketRoomStreams).Bucket(rid)
|
|
||||||
if stream == nil {
|
if stream == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -265,7 +265,7 @@ func btoi(b []byte) uint64 {
|
|||||||
return binary.BigEndian.Uint64(b)
|
return binary.BigEndian.Uint64(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func marshalEvent(evt *event.Event) ([]byte, error) {
|
func marshalEvent(evt *muksevt.Event) ([]byte, error) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
enc := gzip.NewWriter(&buf)
|
enc := gzip.NewWriter(&buf)
|
||||||
if err := gob.NewEncoder(enc).Encode(evt); err != nil {
|
if err := gob.NewEncoder(enc).Encode(evt); err != nil {
|
||||||
@ -277,8 +277,8 @@ func marshalEvent(evt *event.Event) ([]byte, error) {
|
|||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func unmarshalEvent(data []byte) (*event.Event, error) {
|
func unmarshalEvent(data []byte) (*muksevt.Event, error) {
|
||||||
evt := &event.Event{}
|
evt := &muksevt.Event{}
|
||||||
if cmpReader, err := gzip.NewReader(bytes.NewReader(data)); err != nil {
|
if cmpReader, err := gzip.NewReader(bytes.NewReader(data)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if err := gob.NewDecoder(cmpReader).Decode(evt); err != nil {
|
} else if err := gob.NewDecoder(cmpReader).Decode(evt); err != nil {
|
||||||
@ -290,7 +290,7 @@ func unmarshalEvent(data []byte) (*event.Event, error) {
|
|||||||
return evt, nil
|
return evt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func put(streams, eventIDs *bolt.Bucket, evt *event.Event, key uint64) error {
|
func put(streams, eventIDs *bolt.Bucket, evt *muksevt.Event, key uint64) error {
|
||||||
data, err := marshalEvent(evt)
|
data, err := marshalEvent(evt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
177
matrix/matrix.go
177
matrix/matrix.go
@ -36,15 +36,17 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/lib/open"
|
"maunium.net/go/gomuks/lib/open"
|
||||||
"maunium.net/go/gomuks/matrix/event"
|
"maunium.net/go/gomuks/matrix/muksevt"
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
|
"maunium.net/go/mautrix/event"
|
||||||
"maunium.net/go/mautrix/format"
|
"maunium.net/go/mautrix/format"
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/config"
|
"maunium.net/go/gomuks/config"
|
||||||
"maunium.net/go/gomuks/debug"
|
"maunium.net/go/gomuks/debug"
|
||||||
"maunium.net/go/gomuks/interface"
|
"maunium.net/go/gomuks/interface"
|
||||||
"maunium.net/go/gomuks/matrix/pushrules"
|
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
|
"maunium.net/go/mautrix/pushrules"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Container is a wrapper for a mautrix Client and some other stuff.
|
// Container is a wrapper for a mautrix Client and some other stuff.
|
||||||
@ -96,7 +98,8 @@ func (c *Container) InitClient() error {
|
|||||||
c.client = nil
|
c.client = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var mxid, accessToken string
|
var mxid id.UserID
|
||||||
|
var accessToken string
|
||||||
if len(c.config.AccessToken) > 0 {
|
if len(c.config.AccessToken) > 0 {
|
||||||
accessToken = c.config.AccessToken
|
accessToken = c.config.AccessToken
|
||||||
mxid = c.config.UserID
|
mxid = c.config.UserID
|
||||||
@ -180,7 +183,7 @@ func respondHTML(w http.ResponseWriter, status int, message string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) SingleSignOn() error {
|
func (c *Container) SingleSignOn() error {
|
||||||
loginURL := c.client.BuildURLWithQuery([]string{"login", "sso", "redirect"}, map[string]string{
|
loginURL := c.client.BuildURLWithQuery(mautrix.URLPath{"login", "sso", "redirect"}, map[string]string{
|
||||||
"redirectUrl": "http://localhost:29325",
|
"redirectUrl": "http://localhost:29325",
|
||||||
})
|
})
|
||||||
err := open.Open(loginURL)
|
err := open.Open(loginURL)
|
||||||
@ -267,7 +270,7 @@ func (c *Container) Stop() {
|
|||||||
// UpdatePushRules fetches the push notification rules from the server and stores them in the current Session object.
|
// 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 := pushrules.GetPushRules(c.client)
|
resp, err := c.client.GetPushRules()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Print("Failed to fetch push rules:", err)
|
debug.Print("Failed to fetch push rules:", err)
|
||||||
c.config.PushRules = &pushrules.PushRuleset{}
|
c.config.PushRules = &pushrules.PushRuleset{}
|
||||||
@ -285,7 +288,10 @@ func (c *Container) PushRules() *pushrules.PushRuleset {
|
|||||||
return c.config.PushRules
|
return c.config.PushRules
|
||||||
}
|
}
|
||||||
|
|
||||||
var AccountDataGomuksPreferences = mautrix.NewEventType("net.maunium.gomuks.preferences")
|
var AccountDataGomuksPreferences = event.Type{
|
||||||
|
Type: "net.maunium.gomuks.preferences",
|
||||||
|
Class: event.AccountDataEventType,
|
||||||
|
}
|
||||||
|
|
||||||
// OnLogin initializes the syncer and updates the room list.
|
// OnLogin initializes the syncer and updates the room list.
|
||||||
func (c *Container) OnLogin() {
|
func (c *Container) OnLogin() {
|
||||||
@ -295,21 +301,21 @@ func (c *Container) OnLogin() {
|
|||||||
|
|
||||||
debug.Print("Initializing syncer")
|
debug.Print("Initializing syncer")
|
||||||
c.syncer = NewGomuksSyncer(c.config)
|
c.syncer = NewGomuksSyncer(c.config)
|
||||||
c.syncer.OnEventType(mautrix.EventMessage, c.HandleMessage)
|
c.syncer.OnEventType(event.EventMessage, c.HandleMessage)
|
||||||
c.syncer.OnEventType(mautrix.EventEncrypted, c.HandleMessage)
|
c.syncer.OnEventType(event.EventEncrypted, c.HandleMessage)
|
||||||
c.syncer.OnEventType(mautrix.EventSticker, c.HandleMessage)
|
c.syncer.OnEventType(event.EventSticker, c.HandleMessage)
|
||||||
c.syncer.OnEventType(mautrix.EventReaction, c.HandleMessage)
|
c.syncer.OnEventType(event.EventReaction, c.HandleMessage)
|
||||||
c.syncer.OnEventType(mautrix.EventRedaction, c.HandleRedaction)
|
c.syncer.OnEventType(event.EventRedaction, c.HandleRedaction)
|
||||||
c.syncer.OnEventType(mautrix.StateAliases, c.HandleMessage)
|
c.syncer.OnEventType(event.StateAliases, c.HandleMessage)
|
||||||
c.syncer.OnEventType(mautrix.StateCanonicalAlias, c.HandleMessage)
|
c.syncer.OnEventType(event.StateCanonicalAlias, c.HandleMessage)
|
||||||
c.syncer.OnEventType(mautrix.StateTopic, c.HandleMessage)
|
c.syncer.OnEventType(event.StateTopic, c.HandleMessage)
|
||||||
c.syncer.OnEventType(mautrix.StateRoomName, c.HandleMessage)
|
c.syncer.OnEventType(event.StateRoomName, c.HandleMessage)
|
||||||
c.syncer.OnEventType(mautrix.StateMember, c.HandleMembership)
|
c.syncer.OnEventType(event.StateMember, c.HandleMembership)
|
||||||
c.syncer.OnEventType(mautrix.EphemeralEventReceipt, c.HandleReadReceipt)
|
c.syncer.OnEventType(event.EphemeralEventReceipt, c.HandleReadReceipt)
|
||||||
c.syncer.OnEventType(mautrix.EphemeralEventTyping, c.HandleTyping)
|
c.syncer.OnEventType(event.EphemeralEventTyping, c.HandleTyping)
|
||||||
c.syncer.OnEventType(mautrix.AccountDataDirectChats, c.HandleDirectChatInfo)
|
c.syncer.OnEventType(event.AccountDataDirectChats, c.HandleDirectChatInfo)
|
||||||
c.syncer.OnEventType(mautrix.AccountDataPushRules, c.HandlePushRules)
|
c.syncer.OnEventType(event.AccountDataPushRules, c.HandlePushRules)
|
||||||
c.syncer.OnEventType(mautrix.AccountDataRoomTags, c.HandleTag)
|
c.syncer.OnEventType(event.AccountDataRoomTags, c.HandleTag)
|
||||||
c.syncer.OnEventType(AccountDataGomuksPreferences, c.HandlePreferences)
|
c.syncer.OnEventType(AccountDataGomuksPreferences, c.HandlePreferences)
|
||||||
c.syncer.InitDoneCallback = func() {
|
c.syncer.InitDoneCallback = func() {
|
||||||
debug.Print("Initial sync done")
|
debug.Print("Initial sync done")
|
||||||
@ -372,7 +378,7 @@ func (c *Container) Start() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) HandlePreferences(source EventSource, evt *mautrix.Event) {
|
func (c *Container) HandlePreferences(source EventSource, evt *event.Event) {
|
||||||
if source&EventSourceAccountData == 0 {
|
if source&EventSourceAccountData == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -395,18 +401,17 @@ func (c *Container) Preferences() *config.UserPreferences {
|
|||||||
func (c *Container) SendPreferencesToMatrix() {
|
func (c *Container) SendPreferencesToMatrix() {
|
||||||
defer debug.Recover()
|
defer debug.Recover()
|
||||||
debug.Print("Sending updated preferences:", c.config.Preferences)
|
debug.Print("Sending updated preferences:", c.config.Preferences)
|
||||||
u := c.client.BuildURL("user", c.config.UserID, "account_data", AccountDataGomuksPreferences.Type)
|
u := c.client.BuildURL("user", string(c.config.UserID), "account_data", AccountDataGomuksPreferences.Type)
|
||||||
_, err := c.client.MakeRequest("PUT", u, &c.config.Preferences, nil)
|
_, err := c.client.MakeRequest("PUT", u, &c.config.Preferences, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Print("Failed to update preferences:", err)
|
debug.Print("Failed to update preferences:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) HandleRedaction(source EventSource, evt *mautrix.Event) {
|
func (c *Container) HandleRedaction(source EventSource, evt *event.Event) {
|
||||||
room := c.GetOrCreateRoom(evt.RoomID)
|
room := c.GetOrCreateRoom(evt.RoomID)
|
||||||
var redactedEvt *event.Event
|
var redactedEvt *muksevt.Event
|
||||||
err := c.history.Update(room, evt.Redacts, func(redacted *event.Event) error {
|
err := c.history.Update(room, evt.Redacts, func(redacted *muksevt.Event) error {
|
||||||
redacted.Unsigned.RedactedBy = evt.ID
|
|
||||||
redacted.Unsigned.RedactedBecause = evt
|
redacted.Unsigned.RedactedBecause = evt
|
||||||
redactedEvt = redacted
|
redactedEvt = redacted
|
||||||
return nil
|
return nil
|
||||||
@ -430,9 +435,9 @@ func (c *Container) HandleRedaction(source EventSource, evt *mautrix.Event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) HandleEdit(room *rooms.Room, editsID string, editEvent *event.Event) {
|
func (c *Container) HandleEdit(room *rooms.Room, editsID id.EventID, editEvent *muksevt.Event) {
|
||||||
var origEvt *event.Event
|
var origEvt *muksevt.Event
|
||||||
err := c.history.Update(room, editsID, func(evt *event.Event) error {
|
err := c.history.Update(room, editsID, func(evt *muksevt.Event) error {
|
||||||
evt.Gomuks.Edits = append(evt.Gomuks.Edits, editEvent)
|
evt.Gomuks.Edits = append(evt.Gomuks.Edits, editEvent)
|
||||||
origEvt = evt
|
origEvt = evt
|
||||||
return nil
|
return nil
|
||||||
@ -456,10 +461,10 @@ func (c *Container) HandleEdit(room *rooms.Room, editsID string, editEvent *even
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) HandleReaction(room *rooms.Room, reactsTo string, reactEvent *event.Event) {
|
func (c *Container) HandleReaction(room *rooms.Room, reactsTo id.EventID, reactEvent *muksevt.Event) {
|
||||||
rel := reactEvent.Content.GetRelatesTo()
|
rel := reactEvent.Content.GetRelatesTo()
|
||||||
var origEvt *event.Event
|
var origEvt *muksevt.Event
|
||||||
err := c.history.Update(room, reactsTo, func(evt *event.Event) error {
|
err := c.history.Update(room, reactsTo, func(evt *muksevt.Event) error {
|
||||||
if evt.Unsigned.Relations.Annotations.Map == nil {
|
if evt.Unsigned.Relations.Annotations.Map == nil {
|
||||||
evt.Unsigned.Relations.Annotations.Map = make(map[string]int)
|
evt.Unsigned.Relations.Annotations.Map = make(map[string]int)
|
||||||
}
|
}
|
||||||
@ -488,7 +493,7 @@ func (c *Container) HandleReaction(room *rooms.Room, reactsTo string, reactEvent
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleMessage is the event handler for the m.room.message timeline event.
|
// HandleMessage is the event handler for the m.room.message timeline event.
|
||||||
func (c *Container) HandleMessage(source EventSource, mxEvent *mautrix.Event) {
|
func (c *Container) HandleMessage(source EventSource, mxEvent *event.Event) {
|
||||||
room := c.GetOrCreateRoom(mxEvent.RoomID)
|
room := c.GetOrCreateRoom(mxEvent.RoomID)
|
||||||
if source&EventSourceLeave != 0 {
|
if source&EventSourceLeave != 0 {
|
||||||
room.HasLeft = true
|
room.HasLeft = true
|
||||||
@ -498,14 +503,14 @@ func (c *Container) HandleMessage(source EventSource, mxEvent *mautrix.Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if editID := mxEvent.Content.GetRelatesTo().GetReplaceID(); len(editID) > 0 {
|
if editID := mxEvent.Content.GetRelatesTo().GetReplaceID(); len(editID) > 0 {
|
||||||
c.HandleEdit(room, editID, event.Wrap(mxEvent))
|
c.HandleEdit(room, editID, muksevt.Wrap(mxEvent))
|
||||||
return
|
return
|
||||||
} else if reactionID := mxEvent.Content.GetRelatesTo().GetAnnotationID(); mxEvent.Type == mautrix.EventReaction && len(reactionID) > 0 {
|
} else if reactionID := mxEvent.Content.GetRelatesTo().GetAnnotationID(); mxEvent.Type == event.EventReaction && len(reactionID) > 0 {
|
||||||
c.HandleReaction(room, reactionID, event.Wrap(mxEvent))
|
c.HandleReaction(room, reactionID, muksevt.Wrap(mxEvent))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
events, err := c.history.Append(room, []*mautrix.Event{mxEvent})
|
events, err := c.history.Append(room, []*event.Event{mxEvent})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Printf("Failed to add event %s to history: %v", mxEvent.ID, err)
|
debug.Printf("Failed to add event %s to history: %v", mxEvent.ID, err)
|
||||||
}
|
}
|
||||||
@ -549,7 +554,7 @@ func (c *Container) HandleMessage(source EventSource, mxEvent *mautrix.Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleMembership is the event handler for the m.room.member state event.
|
// HandleMembership is the event handler for the m.room.member state event.
|
||||||
func (c *Container) HandleMembership(source EventSource, evt *mautrix.Event) {
|
func (c *Container) HandleMembership(source EventSource, evt *event.Event) {
|
||||||
isLeave := source&EventSourceLeave != 0
|
isLeave := source&EventSourceLeave != 0
|
||||||
isTimeline := source&EventSourceTimeline != 0
|
isTimeline := source&EventSourceTimeline != 0
|
||||||
if isLeave {
|
if isLeave {
|
||||||
@ -558,7 +563,7 @@ func (c *Container) HandleMembership(source EventSource, evt *mautrix.Event) {
|
|||||||
isNonTimelineLeave := isLeave && !isTimeline
|
isNonTimelineLeave := isLeave && !isTimeline
|
||||||
if !c.config.AuthCache.InitialSyncDone && isNonTimelineLeave {
|
if !c.config.AuthCache.InitialSyncDone && isNonTimelineLeave {
|
||||||
return
|
return
|
||||||
} else if evt.StateKey != nil && *evt.StateKey == c.config.UserID {
|
} else if evt.StateKey != nil && id.UserID(*evt.StateKey) == c.config.UserID {
|
||||||
c.processOwnMembershipChange(evt)
|
c.processOwnMembershipChange(evt)
|
||||||
} else if !isTimeline && (!c.config.AuthCache.InitialSyncDone || isLeave) {
|
} else if !isTimeline && (!c.config.AuthCache.InitialSyncDone || isLeave) {
|
||||||
// We don't care about other users' membership events in the initial sync or chats we've left.
|
// We don't care about other users' membership events in the initial sync or chats we've left.
|
||||||
@ -568,9 +573,9 @@ func (c *Container) HandleMembership(source EventSource, evt *mautrix.Event) {
|
|||||||
c.HandleMessage(source, evt)
|
c.HandleMessage(source, evt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) processOwnMembershipChange(evt *mautrix.Event) {
|
func (c *Container) processOwnMembershipChange(evt *event.Event) {
|
||||||
membership := evt.Content.Membership
|
membership := evt.Content.Membership
|
||||||
prevMembership := mautrix.MembershipLeave
|
prevMembership := event.MembershipLeave
|
||||||
if evt.Unsigned.PrevContent != nil {
|
if evt.Unsigned.PrevContent != nil {
|
||||||
prevMembership = evt.Unsigned.PrevContent.Membership
|
prevMembership = evt.Unsigned.PrevContent.Membership
|
||||||
}
|
}
|
||||||
@ -603,7 +608,7 @@ func (c *Container) processOwnMembershipChange(evt *mautrix.Event) {
|
|||||||
c.ui.Render()
|
c.ui.Render()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) parseReadReceipt(evt *mautrix.Event) (largestTimestampEvent string) {
|
func (c *Container) parseReadReceipt(evt *event.Event) (largestTimestampEvent id.EventID) {
|
||||||
var largestTimestamp int64
|
var largestTimestamp int64
|
||||||
for eventID, rawContent := range evt.Content.Raw {
|
for eventID, rawContent := range evt.Content.Raw {
|
||||||
content, ok := rawContent.(map[string]interface{})
|
content, ok := rawContent.(map[string]interface{})
|
||||||
@ -616,7 +621,7 @@ func (c *Container) parseReadReceipt(evt *mautrix.Event) (largestTimestampEvent
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
myInfo, ok := mRead[c.config.UserID].(map[string]interface{})
|
myInfo, ok := mRead[string(c.config.UserID)].(map[string]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -624,13 +629,13 @@ func (c *Container) parseReadReceipt(evt *mautrix.Event) (largestTimestampEvent
|
|||||||
ts, ok := myInfo["ts"].(float64)
|
ts, ok := myInfo["ts"].(float64)
|
||||||
if int64(ts) > largestTimestamp {
|
if int64(ts) > largestTimestamp {
|
||||||
largestTimestamp = int64(ts)
|
largestTimestamp = int64(ts)
|
||||||
largestTimestampEvent = eventID
|
largestTimestampEvent = id.EventID(eventID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) HandleReadReceipt(source EventSource, evt *mautrix.Event) {
|
func (c *Container) HandleReadReceipt(source EventSource, evt *event.Event) {
|
||||||
if source&EventSourceLeave != 0 {
|
if source&EventSourceLeave != 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -649,7 +654,7 @@ func (c *Container) HandleReadReceipt(source EventSource, evt *mautrix.Event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) parseDirectChatInfo(evt *mautrix.Event) map[*rooms.Room]bool {
|
func (c *Container) parseDirectChatInfo(evt *event.Event) map[*rooms.Room]bool {
|
||||||
directChats := make(map[*rooms.Room]bool)
|
directChats := make(map[*rooms.Room]bool)
|
||||||
for _, rawRoomIDList := range evt.Content.Raw {
|
for _, rawRoomIDList := range evt.Content.Raw {
|
||||||
roomIDList, ok := rawRoomIDList.([]interface{})
|
roomIDList, ok := rawRoomIDList.([]interface{})
|
||||||
@ -663,7 +668,7 @@ func (c *Container) parseDirectChatInfo(evt *mautrix.Event) map[*rooms.Room]bool
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
room := c.GetOrCreateRoom(roomID)
|
room := c.GetOrCreateRoom(id.RoomID(roomID))
|
||||||
if room != nil && !room.HasLeft {
|
if room != nil && !room.HasLeft {
|
||||||
directChats[room] = true
|
directChats[room] = true
|
||||||
}
|
}
|
||||||
@ -672,7 +677,7 @@ func (c *Container) parseDirectChatInfo(evt *mautrix.Event) map[*rooms.Room]bool
|
|||||||
return directChats
|
return directChats
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) HandleDirectChatInfo(_ EventSource, evt *mautrix.Event) {
|
func (c *Container) HandleDirectChatInfo(_ EventSource, evt *event.Event) {
|
||||||
directChats := c.parseDirectChatInfo(evt)
|
directChats := c.parseDirectChatInfo(evt)
|
||||||
for _, room := range c.config.Rooms.Map {
|
for _, room := range c.config.Rooms.Map {
|
||||||
shouldBeDirect := directChats[room]
|
shouldBeDirect := directChats[room]
|
||||||
@ -686,7 +691,7 @@ func (c *Container) HandleDirectChatInfo(_ EventSource, evt *mautrix.Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandlePushRules is the event handler for the m.push_rules account data event.
|
// HandlePushRules is the event handler for the m.push_rules account data event.
|
||||||
func (c *Container) HandlePushRules(_ EventSource, evt *mautrix.Event) {
|
func (c *Container) HandlePushRules(_ EventSource, evt *event.Event) {
|
||||||
debug.Print("Received updated push rules")
|
debug.Print("Received updated push rules")
|
||||||
var err error
|
var err error
|
||||||
c.config.PushRules, err = pushrules.EventToPushRules(evt)
|
c.config.PushRules, err = pushrules.EventToPushRules(evt)
|
||||||
@ -698,7 +703,7 @@ func (c *Container) HandlePushRules(_ EventSource, evt *mautrix.Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleTag is the event handler for the m.tag account data event.
|
// HandleTag is the event handler for the m.tag account data event.
|
||||||
func (c *Container) HandleTag(_ EventSource, evt *mautrix.Event) {
|
func (c *Container) HandleTag(_ EventSource, evt *event.Event) {
|
||||||
debug.Printf("Received tags for %s: %s -- %s", evt.RoomID, evt.Content.RoomTags, string(evt.Content.VeryRaw))
|
debug.Printf("Received tags for %s: %s -- %s", evt.RoomID, evt.Content.RoomTags, string(evt.Content.VeryRaw))
|
||||||
room := c.GetOrCreateRoom(evt.RoomID)
|
room := c.GetOrCreateRoom(evt.RoomID)
|
||||||
|
|
||||||
@ -724,24 +729,24 @@ func (c *Container) HandleTag(_ EventSource, evt *mautrix.Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleTyping is the event handler for the m.typing event.
|
// HandleTyping is the event handler for the m.typing event.
|
||||||
func (c *Container) HandleTyping(_ EventSource, evt *mautrix.Event) {
|
func (c *Container) HandleTyping(_ EventSource, evt *event.Event) {
|
||||||
if !c.config.AuthCache.InitialSyncDone {
|
if !c.config.AuthCache.InitialSyncDone {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.ui.MainView().SetTyping(evt.RoomID, evt.Content.TypingUserIDs)
|
c.ui.MainView().SetTyping(evt.RoomID, evt.Content.TypingUserIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) MarkRead(roomID, eventID string) {
|
func (c *Container) MarkRead(roomID id.RoomID, eventID id.EventID) {
|
||||||
urlPath := c.client.BuildURL("rooms", roomID, "receipt", "m.read", eventID)
|
urlPath := c.client.BuildURL("rooms", roomID, "receipt", "m.read", eventID)
|
||||||
_, _ = c.client.MakeRequest("POST", urlPath, struct{}{}, nil)
|
_, _ = c.client.MakeRequest("POST", urlPath, struct{}{}, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) PrepareMarkdownMessage(roomID string, msgtype mautrix.MessageType, text, html string, rel *ifc.Relation) *event.Event {
|
func (c *Container) PrepareMarkdownMessage(roomID id.RoomID, msgtype event.MessageType, text, html string, rel *ifc.Relation) *muksevt.Event {
|
||||||
var content mautrix.Content
|
var content event.Content
|
||||||
if html != "" {
|
if html != "" {
|
||||||
content = mautrix.Content{
|
content = event.Content{
|
||||||
FormattedBody: html,
|
FormattedBody: html,
|
||||||
Format: mautrix.FormatHTML,
|
Format: event.FormatHTML,
|
||||||
Body: text,
|
Body: text,
|
||||||
MsgType: msgtype,
|
MsgType: msgtype,
|
||||||
}
|
}
|
||||||
@ -750,49 +755,49 @@ func (c *Container) PrepareMarkdownMessage(roomID string, msgtype mautrix.Messag
|
|||||||
content.MsgType = msgtype
|
content.MsgType = msgtype
|
||||||
}
|
}
|
||||||
|
|
||||||
if rel != nil && rel.Type == mautrix.RelReplace {
|
if rel != nil && rel.Type == event.RelReplace {
|
||||||
contentCopy := content
|
contentCopy := content
|
||||||
content.NewContent = &contentCopy
|
content.NewContent = &contentCopy
|
||||||
content.Body = "* " + content.Body
|
content.Body = "* " + content.Body
|
||||||
if len(content.FormattedBody) > 0 {
|
if len(content.FormattedBody) > 0 {
|
||||||
content.FormattedBody = "* " + content.FormattedBody
|
content.FormattedBody = "* " + content.FormattedBody
|
||||||
}
|
}
|
||||||
content.RelatesTo = &mautrix.RelatesTo{
|
content.RelatesTo = &event.RelatesTo{
|
||||||
Type: mautrix.RelReplace,
|
Type: event.RelReplace,
|
||||||
EventID: rel.Event.ID,
|
EventID: rel.Event.ID,
|
||||||
}
|
}
|
||||||
} else if rel != nil && rel.Type == mautrix.RelReference {
|
} else if rel != nil && rel.Type == event.RelReference {
|
||||||
content.SetReply(rel.Event.Event)
|
content.SetReply(rel.Event.Event)
|
||||||
}
|
}
|
||||||
|
|
||||||
txnID := c.client.TxnID()
|
txnID := c.client.TxnID()
|
||||||
localEcho := event.Wrap(&mautrix.Event{
|
localEcho := muksevt.Wrap(&event.Event{
|
||||||
ID: txnID,
|
ID: id.EventID(txnID),
|
||||||
Sender: c.config.UserID,
|
Sender: c.config.UserID,
|
||||||
Type: mautrix.EventMessage,
|
Type: event.EventMessage,
|
||||||
Timestamp: time.Now().UnixNano() / 1e6,
|
Timestamp: time.Now().UnixNano() / 1e6,
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
Content: content,
|
Content: content,
|
||||||
Unsigned: mautrix.Unsigned{
|
Unsigned: event.Unsigned{
|
||||||
TransactionID: txnID,
|
TransactionID: txnID,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
localEcho.Gomuks.OutgoingState = event.StateLocalEcho
|
localEcho.Gomuks.OutgoingState = muksevt.StateLocalEcho
|
||||||
if rel != nil && rel.Type == mautrix.RelReplace {
|
if rel != nil && rel.Type == event.RelReplace {
|
||||||
localEcho.ID = rel.Event.ID
|
localEcho.ID = rel.Event.ID
|
||||||
localEcho.Gomuks.Edits = []*event.Event{localEcho}
|
localEcho.Gomuks.Edits = []*muksevt.Event{localEcho}
|
||||||
}
|
}
|
||||||
return localEcho
|
return localEcho
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) Redact(roomID, eventID, reason string) error {
|
func (c *Container) Redact(roomID id.RoomID, eventID id.EventID, reason string) error {
|
||||||
defer debug.Recover()
|
defer debug.Recover()
|
||||||
_, err := c.client.RedactEvent(roomID, eventID, mautrix.ReqRedact{Reason: reason})
|
_, err := c.client.RedactEvent(roomID, eventID, mautrix.ReqRedact{Reason: reason})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendMessage sends the given event.
|
// SendMessage sends the given event.
|
||||||
func (c *Container) SendEvent(event *event.Event) (string, error) {
|
func (c *Container) SendEvent(event *muksevt.Event) (id.EventID, error) {
|
||||||
defer debug.Recover()
|
defer debug.Recover()
|
||||||
|
|
||||||
c.client.UserTyping(event.RoomID, false, 0)
|
c.client.UserTyping(event.RoomID, false, 0)
|
||||||
@ -804,13 +809,13 @@ func (c *Container) SendEvent(event *event.Event) (string, error) {
|
|||||||
return resp.EventID, nil
|
return resp.EventID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) sendTypingAsync(roomID string, typing bool, timeout int64) {
|
func (c *Container) sendTypingAsync(roomID id.RoomID, typing bool, timeout int64) {
|
||||||
defer debug.Recover()
|
defer debug.Recover()
|
||||||
_, _ = c.client.UserTyping(roomID, typing, timeout)
|
_, _ = c.client.UserTyping(roomID, typing, timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendTyping sets whether or not the user is typing in the given room.
|
// 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 id.RoomID, typing bool) {
|
||||||
ts := time.Now().Unix()
|
ts := time.Now().Unix()
|
||||||
if (c.typing > ts && typing) || (c.typing == 0 && !typing) {
|
if (c.typing > ts && typing) || (c.typing == 0 && !typing) {
|
||||||
return
|
return
|
||||||
@ -836,8 +841,8 @@ func (c *Container) CreateRoom(req *mautrix.ReqCreateRoom) (*rooms.Room, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// JoinRoom makes the current user try to join the given room.
|
// JoinRoom makes the current user try to join the given room.
|
||||||
func (c *Container) JoinRoom(roomID, server string) (*rooms.Room, error) {
|
func (c *Container) JoinRoom(roomID id.RoomID, server string) (*rooms.Room, error) {
|
||||||
resp, err := c.client.JoinRoom(roomID, server, nil)
|
resp, err := c.client.JoinRoom(string(roomID), server, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -848,7 +853,7 @@ func (c *Container) JoinRoom(roomID, server string) (*rooms.Room, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LeaveRoom makes the current user leave the given room.
|
// LeaveRoom makes the current user leave the given room.
|
||||||
func (c *Container) LeaveRoom(roomID string) error {
|
func (c *Container) LeaveRoom(roomID id.RoomID) error {
|
||||||
_, err := c.client.LeaveRoom(roomID)
|
_, err := c.client.LeaveRoom(roomID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -873,7 +878,7 @@ func (c *Container) FetchMembers(room *rooms.Room) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetHistory fetches room history.
|
// GetHistory fetches room history.
|
||||||
func (c *Container) GetHistory(room *rooms.Room, limit int) ([]*event.Event, error) {
|
func (c *Container) GetHistory(room *rooms.Room, limit int) ([]*muksevt.Event, error) {
|
||||||
events, err := c.history.Load(room, limit)
|
events, err := c.history.Load(room, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -893,7 +898,7 @@ func (c *Container) GetHistory(room *rooms.Room, limit int) ([]*event.Event, err
|
|||||||
room.PrevBatch = resp.End
|
room.PrevBatch = resp.End
|
||||||
c.config.Rooms.Put(room)
|
c.config.Rooms.Put(room)
|
||||||
if len(resp.Chunk) == 0 {
|
if len(resp.Chunk) == 0 {
|
||||||
return []*event.Event{}, nil
|
return []*muksevt.Event{}, nil
|
||||||
}
|
}
|
||||||
events, err = c.history.Prepend(room, resp.Chunk)
|
events, err = c.history.Prepend(room, resp.Chunk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -902,7 +907,7 @@ func (c *Container) GetHistory(room *rooms.Room, limit int) ([]*event.Event, err
|
|||||||
return events, nil
|
return events, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) GetEvent(room *rooms.Room, eventID string) (*event.Event, error) {
|
func (c *Container) GetEvent(room *rooms.Room, eventID id.EventID) (*muksevt.Event, error) {
|
||||||
evt, err := c.history.Get(room, eventID)
|
evt, err := c.history.Get(room, eventID)
|
||||||
if err != nil && err != EventNotFoundError {
|
if err != nil && err != EventNotFoundError {
|
||||||
debug.Printf("Failed to get event %s from local cache: %v", eventID, err)
|
debug.Printf("Failed to get event %s from local cache: %v", eventID, err)
|
||||||
@ -914,18 +919,18 @@ func (c *Container) GetEvent(room *rooms.Room, eventID string) (*event.Event, er
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
evt = event.Wrap(mxEvent)
|
evt = muksevt.Wrap(mxEvent)
|
||||||
debug.Printf("Loaded event %s from server", eventID)
|
debug.Printf("Loaded event %s from server", eventID)
|
||||||
return evt, nil
|
return evt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOrCreateRoom gets the room instance stored in the session.
|
// GetOrCreateRoom gets the room instance stored in the session.
|
||||||
func (c *Container) GetOrCreateRoom(roomID string) *rooms.Room {
|
func (c *Container) GetOrCreateRoom(roomID id.RoomID) *rooms.Room {
|
||||||
return c.config.Rooms.GetOrCreate(roomID)
|
return c.config.Rooms.GetOrCreate(roomID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRoom gets the room instance stored in the session.
|
// GetRoom gets the room instance stored in the session.
|
||||||
func (c *Container) GetRoom(roomID string) *rooms.Room {
|
func (c *Container) GetRoom(roomID id.RoomID) *rooms.Room {
|
||||||
return c.config.Rooms.Get(roomID)
|
return c.config.Rooms.Get(roomID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -949,7 +954,7 @@ func cp(src, dst string) error {
|
|||||||
return out.Close()
|
return out.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) DownloadToDisk(uri mautrix.ContentURI, target string) (fullPath string, err error) {
|
func (c *Container) DownloadToDisk(uri id.ContentURI, target string) (fullPath string, err error) {
|
||||||
cachePath := c.GetCachePath(uri)
|
cachePath := c.GetCachePath(uri)
|
||||||
if target == "" {
|
if target == "" {
|
||||||
fullPath = cachePath
|
fullPath = cachePath
|
||||||
@ -994,7 +999,7 @@ func (c *Container) DownloadToDisk(uri mautrix.ContentURI, target string) (fullP
|
|||||||
// Download fetches the given Matrix content (mxc) URL and returns the data, homeserver, file ID and potential errors.
|
// Download fetches the given Matrix content (mxc) URL and returns the data, homeserver, file ID and potential errors.
|
||||||
//
|
//
|
||||||
// The file will be either read from the media cache (if found) or downloaded from the server.
|
// The file will be either read from the media cache (if found) or downloaded from the server.
|
||||||
func (c *Container) Download(uri mautrix.ContentURI) (data []byte, err error) {
|
func (c *Container) Download(uri id.ContentURI) (data []byte, err error) {
|
||||||
cacheFile := c.GetCachePath(uri)
|
cacheFile := c.GetCachePath(uri)
|
||||||
var info os.FileInfo
|
var info os.FileInfo
|
||||||
if info, err = os.Stat(cacheFile); err == nil && !info.IsDir() {
|
if info, err = os.Stat(cacheFile); err == nil && !info.IsDir() {
|
||||||
@ -1008,7 +1013,7 @@ func (c *Container) Download(uri mautrix.ContentURI) (data []byte, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) GetDownloadURL(uri mautrix.ContentURI) string {
|
func (c *Container) GetDownloadURL(uri id.ContentURI) string {
|
||||||
dlURL, _ := url.Parse(c.client.HomeserverURL.String())
|
dlURL, _ := url.Parse(c.client.HomeserverURL.String())
|
||||||
if dlURL.Scheme == "" {
|
if dlURL.Scheme == "" {
|
||||||
dlURL.Scheme = "https"
|
dlURL.Scheme = "https"
|
||||||
@ -1017,7 +1022,7 @@ func (c *Container) GetDownloadURL(uri mautrix.ContentURI) string {
|
|||||||
return dlURL.String()
|
return dlURL.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) download(uri mautrix.ContentURI, cacheFile string) (data []byte, err error) {
|
func (c *Container) download(uri id.ContentURI, cacheFile string) (data []byte, err error) {
|
||||||
var resp *http.Response
|
var resp *http.Response
|
||||||
resp, err = c.client.Client.Get(c.GetDownloadURL(uri))
|
resp, err = c.client.Client.Get(c.GetDownloadURL(uri))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1039,7 +1044,7 @@ func (c *Container) download(uri mautrix.ContentURI, cacheFile string) (data []b
|
|||||||
|
|
||||||
// GetCachePath gets the path to the cached version of the given homeserver:fileID combination.
|
// GetCachePath gets the path to the cached version of the given homeserver:fileID combination.
|
||||||
// The file may or may not exist, use Download() to ensure it has been cached.
|
// The file may or may not exist, use Download() to ensure it has been cached.
|
||||||
func (c *Container) GetCachePath(uri mautrix.ContentURI) string {
|
func (c *Container) GetCachePath(uri id.ContentURI) string {
|
||||||
dir := filepath.Join(c.config.MediaDir, uri.Homeserver)
|
dir := filepath.Join(c.config.MediaDir, uri.Homeserver)
|
||||||
|
|
||||||
err := os.MkdirAll(dir, 0700)
|
err := os.MkdirAll(dir, 0700)
|
||||||
|
@ -1,232 +0,0 @@
|
|||||||
// gomuks - A terminal Matrix client written in Go.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package matrix
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"maunium.net/go/gomuks/config"
|
|
||||||
"maunium.net/go/mautrix"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestContainer_InitClient_Empty(t *testing.T) {
|
|
||||||
defer os.RemoveAll("/tmp/gomuks-mxtest-0")
|
|
||||||
os.MkdirAll("/tmp/gomuks-mxtest-0", 0700)
|
|
||||||
cfg := config.NewConfig("/tmp/gomuks-mxtest-0", "/tmp/gomuks-mxtest-0")
|
|
||||||
cfg.HS = "https://matrix.org"
|
|
||||||
c := Container{config: cfg}
|
|
||||||
assert.Nil(t, c.InitClient())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestContainer_GetCachePath(t *testing.T) {
|
|
||||||
defer os.RemoveAll("/tmp/gomuks-mxtest-1")
|
|
||||||
cfg := config.NewConfig("/tmp/gomuks-mxtest-1", "/tmp/gomuks-mxtest-1")
|
|
||||||
c := Container{config: cfg}
|
|
||||||
assert.Equal(t, "/tmp/gomuks-mxtest-1/media/maunium.net/foobar", c.GetCachePath("maunium.net", "foobar"))
|
|
||||||
}
|
|
||||||
|
|
||||||
/* FIXME probably not applicable anymore
|
|
||||||
func TestContainer_SendMarkdownMessage_NoMarkdown(t *testing.T) {
|
|
||||||
c := Container{client: mockClient(func(req *http.Request) (*http.Response, error) {
|
|
||||||
if req.Method != http.MethodPut || !strings.HasPrefix(req.URL.Path, "/_matrix/client/r0/rooms/!foo:example.com/send/m.room.message/") {
|
|
||||||
return nil, fmt.Errorf("unexpected query: %s %s", req.Method, req.URL.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
body := parseBody(req)
|
|
||||||
assert.Equal(t, "m.text", body["msgtype"])
|
|
||||||
assert.Equal(t, "test message", body["body"])
|
|
||||||
return mockResponse(http.StatusOK, `{"event_id": "!foobar1:example.com"}`), nil
|
|
||||||
})}
|
|
||||||
|
|
||||||
event := c.PrepareMarkdownMessage("!foo:example.com", "m.text", "test message")
|
|
||||||
evtID, err := c.SendEvent(event)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, "!foobar1:example.com", evtID)
|
|
||||||
}*/
|
|
||||||
|
|
||||||
func TestContainer_SendMarkdownMessage_WithMarkdown(t *testing.T) {
|
|
||||||
c := Container{client: mockClient(func(req *http.Request) (*http.Response, error) {
|
|
||||||
if req.Method != http.MethodPut || !strings.HasPrefix(req.URL.Path, "/_matrix/client/r0/rooms/!foo:example.com/send/m.room.message/") {
|
|
||||||
return nil, fmt.Errorf("unexpected query: %s %s", req.Method, req.URL.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
body := parseBody(req)
|
|
||||||
assert.Equal(t, "m.text", body["msgtype"])
|
|
||||||
assert.Equal(t, "**formatted** test _message_", body["body"])
|
|
||||||
assert.Equal(t, "<p><strong>formatted</strong> <u>test</u> <em>message</em></p>\n", body["formatted_body"])
|
|
||||||
return mockResponse(http.StatusOK, `{"event_id": "!foobar2:example.com"}`), nil
|
|
||||||
}), config: &config.Config{UserID: "@user:example.com"}}
|
|
||||||
|
|
||||||
event := c.PrepareMarkdownMessage("!foo:example.com", "m.text", "**formatted** <u>test</u> _message_")
|
|
||||||
evtID, err := c.SendEvent(event)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, "!foobar2:example.com", evtID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestContainer_SendTyping(t *testing.T) {
|
|
||||||
var calls []mautrix.ReqTyping
|
|
||||||
c := Container{client: mockClient(func(req *http.Request) (*http.Response, error) {
|
|
||||||
if req.Method != http.MethodPut || req.URL.Path != "/_matrix/client/r0/rooms/!foo:example.com/typing/@user:example.com" {
|
|
||||||
return nil, fmt.Errorf("unexpected query: %s %s", req.Method, req.URL.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
rawBody, err := ioutil.ReadAll(req.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
call := mautrix.ReqTyping{}
|
|
||||||
err = json.Unmarshal(rawBody, &call)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
calls = append(calls, call)
|
|
||||||
|
|
||||||
return mockResponse(http.StatusOK, `{}`), nil
|
|
||||||
})}
|
|
||||||
|
|
||||||
c.SendTyping("!foo:example.com", true)
|
|
||||||
c.SendTyping("!foo:example.com", true)
|
|
||||||
c.SendTyping("!foo:example.com", true)
|
|
||||||
c.SendTyping("!foo:example.com", false)
|
|
||||||
c.SendTyping("!foo:example.com", true)
|
|
||||||
c.SendTyping("!foo:example.com", false)
|
|
||||||
assert.Len(t, calls, 4)
|
|
||||||
assert.True(t, calls[0].Typing)
|
|
||||||
assert.False(t, calls[1].Typing)
|
|
||||||
assert.True(t, calls[2].Typing)
|
|
||||||
assert.False(t, calls[3].Typing)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestContainer_JoinRoom(t *testing.T) {
|
|
||||||
defer os.RemoveAll("/tmp/gomuks-mxtest-2")
|
|
||||||
cfg := config.NewConfig("/tmp/gomuks-mxtest-2", "/tmp/gomuks-mxtest-2")
|
|
||||||
c := Container{client: mockClient(func(req *http.Request) (*http.Response, error) {
|
|
||||||
if req.Method == http.MethodPost && req.URL.Path == "/_matrix/client/r0/join/!foo:example.com" {
|
|
||||||
return mockResponse(http.StatusOK, `{"room_id": "!foo:example.com"}`), nil
|
|
||||||
} else if req.Method == http.MethodPost && req.URL.Path == "/_matrix/client/r0/rooms/!foo:example.com/leave" {
|
|
||||||
return mockResponse(http.StatusOK, `{}`), nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("unexpected query: %s %s", req.Method, req.URL.Path)
|
|
||||||
}), config: cfg}
|
|
||||||
|
|
||||||
room, err := c.JoinRoom("!foo:example.com", "")
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, "!foo:example.com", room.ID)
|
|
||||||
assert.False(t, room.HasLeft)
|
|
||||||
|
|
||||||
err = c.LeaveRoom("!foo:example.com")
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.True(t, room.HasLeft)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestContainer_Download(t *testing.T) {
|
|
||||||
defer os.RemoveAll("/tmp/gomuks-mxtest-3")
|
|
||||||
cfg := config.NewConfig("/tmp/gomuks-mxtest-3", "/tmp/gomuks-mxtest-3")
|
|
||||||
cfg.LoadAll()
|
|
||||||
callCounter := 0
|
|
||||||
c := Container{client: mockClient(func(req *http.Request) (*http.Response, error) {
|
|
||||||
if req.Method != http.MethodGet || req.URL.Path != "/_matrix/media/v1/download/example.com/foobar" {
|
|
||||||
return nil, fmt.Errorf("unexpected query: %s %s", req.Method, req.URL.Path)
|
|
||||||
}
|
|
||||||
callCounter++
|
|
||||||
return mockResponse(http.StatusOK, `example file`), nil
|
|
||||||
}), config: cfg}
|
|
||||||
|
|
||||||
// Check that download works
|
|
||||||
data, hs, id, err := c.Download("mxc://example.com/foobar")
|
|
||||||
assert.Equal(t, "example.com", hs)
|
|
||||||
assert.Equal(t, "foobar", id)
|
|
||||||
assert.Equal(t, 1, callCounter)
|
|
||||||
assert.Equal(t, []byte("example file"), data)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
// Check that cache works
|
|
||||||
data, _, _, err = c.Download("mxc://example.com/foobar")
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, []byte("example file"), data)
|
|
||||||
assert.Equal(t, 1, callCounter)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestContainer_Download_InvalidURL(t *testing.T) {
|
|
||||||
c := Container{}
|
|
||||||
data, hs, id, err := c.Download("mxc://invalid mxc")
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
assert.Empty(t, id)
|
|
||||||
assert.Empty(t, hs)
|
|
||||||
assert.Empty(t, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* FIXME
|
|
||||||
func TestContainer_GetHistory(t *testing.T) {
|
|
||||||
c := Container{client: mockClient(func(req *http.Request) (*http.Response, error) {
|
|
||||||
if req.Method != http.MethodGet || req.URL.Path != "/_matrix/client/r0/rooms/!foo:maunium.net/messages" {
|
|
||||||
return nil, fmt.Errorf("unexpected query: %s %s", req.Method, req.URL.Path)
|
|
||||||
}
|
|
||||||
return mockResponse(http.StatusOK, `{"start": "123", "end": "456", "chunk": [{"event_id": "it works"}]}`), nil
|
|
||||||
})}
|
|
||||||
|
|
||||||
history, prevBatch, err := c.GetHistory("!foo:maunium.net", "123", 5)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, "it works", history[0].ID)
|
|
||||||
assert.Equal(t, "456", prevBatch)
|
|
||||||
}*/
|
|
||||||
|
|
||||||
func mockClient(fn func(*http.Request) (*http.Response, error)) *mautrix.Client {
|
|
||||||
client, _ := mautrix.NewClient("https://example.com", "@user:example.com", "foobar")
|
|
||||||
client.Client = &http.Client{Transport: MockRoundTripper{RT: fn}}
|
|
||||||
return client
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseBody(req *http.Request) map[string]interface{} {
|
|
||||||
rawBody, err := ioutil.ReadAll(req.Body)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
data := make(map[string]interface{})
|
|
||||||
|
|
||||||
err = json.Unmarshal(rawBody, &data)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
func mockResponse(status int, body string) *http.Response {
|
|
||||||
return &http.Response{
|
|
||||||
StatusCode: status,
|
|
||||||
Body: ioutil.NopCloser(strings.NewReader(body)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type MockRoundTripper struct {
|
|
||||||
RT func(*http.Request) (*http.Response, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t MockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
||||||
return t.RT(req)
|
|
||||||
}
|
|
@ -14,14 +14,14 @@
|
|||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package event
|
package muksevt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix/event"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
*mautrix.Event
|
*event.Event
|
||||||
Gomuks GomuksContent `json:"-"`
|
Gomuks GomuksContent `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ func (evt *Event) SomewhatDangerousCopy() *Event {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Wrap(event *mautrix.Event) *Event {
|
func Wrap(event *event.Event) *Event {
|
||||||
return &Event{Event: event}
|
return &Event{Event: event}
|
||||||
}
|
}
|
||||||
|
|
@ -1,134 +0,0 @@
|
|||||||
// gomuks - A terminal Matrix client written in Go.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://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)
|
|
||||||
}
|
|
||||||
data := string(action.Action)
|
|
||||||
return json.Marshal(&data)
|
|
||||||
}
|
|
@ -1,210 +0,0 @@
|
|||||||
// gomuks - A terminal Matrix client written in Go.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package pushrules_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"maunium.net/go/gomuks/matrix/pushrules"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPushActionArray_Should_EmptyArrayReturnsDefaults(t *testing.T) {
|
|
||||||
should := pushrules.PushActionArray{}.Should()
|
|
||||||
assert.False(t, should.NotifySpecified)
|
|
||||||
assert.False(t, should.Notify)
|
|
||||||
assert.False(t, should.Highlight)
|
|
||||||
assert.False(t, should.PlaySound)
|
|
||||||
assert.Empty(t, should.SoundName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushActionArray_Should_MixedArrayReturnsExpected1(t *testing.T) {
|
|
||||||
should := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionNotify},
|
|
||||||
{Action: pushrules.ActionSetTweak, Tweak: pushrules.TweakHighlight},
|
|
||||||
{Action: pushrules.ActionSetTweak, Tweak: pushrules.TweakSound, Value: "ping"},
|
|
||||||
}.Should()
|
|
||||||
assert.True(t, should.NotifySpecified)
|
|
||||||
assert.True(t, should.Notify)
|
|
||||||
assert.True(t, should.Highlight)
|
|
||||||
assert.True(t, should.PlaySound)
|
|
||||||
assert.Equal(t, "ping", should.SoundName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushActionArray_Should_MixedArrayReturnsExpected2(t *testing.T) {
|
|
||||||
should := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionDontNotify},
|
|
||||||
{Action: pushrules.ActionSetTweak, Tweak: pushrules.TweakHighlight, Value: false},
|
|
||||||
{Action: pushrules.ActionSetTweak, Tweak: pushrules.TweakSound, Value: ""},
|
|
||||||
}.Should()
|
|
||||||
assert.True(t, should.NotifySpecified)
|
|
||||||
assert.False(t, should.Notify)
|
|
||||||
assert.False(t, should.Highlight)
|
|
||||||
assert.False(t, should.PlaySound)
|
|
||||||
assert.Empty(t, should.SoundName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushActionArray_Should_NotifySet(t *testing.T) {
|
|
||||||
should := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionNotify},
|
|
||||||
}.Should()
|
|
||||||
assert.True(t, should.NotifySpecified)
|
|
||||||
assert.True(t, should.Notify)
|
|
||||||
assert.False(t, should.Highlight)
|
|
||||||
assert.False(t, should.PlaySound)
|
|
||||||
assert.Empty(t, should.SoundName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushActionArray_Should_NotifyAndCoalesceDoTheSameThing(t *testing.T) {
|
|
||||||
should1 := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionNotify},
|
|
||||||
}.Should()
|
|
||||||
should2 := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionCoalesce},
|
|
||||||
}.Should()
|
|
||||||
assert.Equal(t, should1, should2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushActionArray_Should_DontNotify(t *testing.T) {
|
|
||||||
should := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionDontNotify},
|
|
||||||
}.Should()
|
|
||||||
assert.True(t, should.NotifySpecified)
|
|
||||||
assert.False(t, should.Notify)
|
|
||||||
assert.False(t, should.Highlight)
|
|
||||||
assert.False(t, should.PlaySound)
|
|
||||||
assert.Empty(t, should.SoundName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushActionArray_Should_HighlightBlank(t *testing.T) {
|
|
||||||
should := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionSetTweak, Tweak: pushrules.TweakHighlight},
|
|
||||||
}.Should()
|
|
||||||
assert.False(t, should.NotifySpecified)
|
|
||||||
assert.False(t, should.Notify)
|
|
||||||
assert.True(t, should.Highlight)
|
|
||||||
assert.False(t, should.PlaySound)
|
|
||||||
assert.Empty(t, should.SoundName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushActionArray_Should_HighlightFalse(t *testing.T) {
|
|
||||||
should := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionSetTweak, Tweak: pushrules.TweakHighlight, Value: false},
|
|
||||||
}.Should()
|
|
||||||
assert.False(t, should.NotifySpecified)
|
|
||||||
assert.False(t, should.Notify)
|
|
||||||
assert.False(t, should.Highlight)
|
|
||||||
assert.False(t, should.PlaySound)
|
|
||||||
assert.Empty(t, should.SoundName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushActionArray_Should_SoundName(t *testing.T) {
|
|
||||||
should := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionSetTweak, Tweak: pushrules.TweakSound, Value: "ping"},
|
|
||||||
}.Should()
|
|
||||||
assert.False(t, should.NotifySpecified)
|
|
||||||
assert.False(t, should.Notify)
|
|
||||||
assert.False(t, should.Highlight)
|
|
||||||
assert.True(t, should.PlaySound)
|
|
||||||
assert.Equal(t, "ping", should.SoundName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushActionArray_Should_SoundNameEmpty(t *testing.T) {
|
|
||||||
should := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionSetTweak, Tweak: pushrules.TweakSound, Value: ""},
|
|
||||||
}.Should()
|
|
||||||
assert.False(t, should.NotifySpecified)
|
|
||||||
assert.False(t, should.Notify)
|
|
||||||
assert.False(t, should.Highlight)
|
|
||||||
assert.False(t, should.PlaySound)
|
|
||||||
assert.Empty(t, should.SoundName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushAction_UnmarshalJSON_InvalidJSONFails(t *testing.T) {
|
|
||||||
pa := &pushrules.PushAction{}
|
|
||||||
err := pa.UnmarshalJSON([]byte("Not JSON"))
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushAction_UnmarshalJSON_InvalidTypeDoesNothing(t *testing.T) {
|
|
||||||
pa := &pushrules.PushAction{
|
|
||||||
Action: pushrules.PushActionType("unchanged"),
|
|
||||||
Tweak: pushrules.PushActionTweak("unchanged"),
|
|
||||||
Value: "unchanged",
|
|
||||||
}
|
|
||||||
|
|
||||||
err := pa.UnmarshalJSON([]byte(`{"foo": "bar"}`))
|
|
||||||
assert.Nil(t, err)
|
|
||||||
err = pa.UnmarshalJSON([]byte(`9001`))
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, pushrules.PushActionType("unchanged"), pa.Action)
|
|
||||||
assert.Equal(t, pushrules.PushActionTweak("unchanged"), pa.Tweak)
|
|
||||||
assert.Equal(t, "unchanged", pa.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushAction_UnmarshalJSON_StringChangesActionType(t *testing.T) {
|
|
||||||
pa := &pushrules.PushAction{
|
|
||||||
Action: pushrules.PushActionType("unchanged"),
|
|
||||||
Tweak: pushrules.PushActionTweak("unchanged"),
|
|
||||||
Value: "unchanged",
|
|
||||||
}
|
|
||||||
|
|
||||||
err := pa.UnmarshalJSON([]byte(`"foo"`))
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, pushrules.PushActionType("foo"), pa.Action)
|
|
||||||
assert.Equal(t, pushrules.PushActionTweak("unchanged"), pa.Tweak)
|
|
||||||
assert.Equal(t, "unchanged", pa.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushAction_UnmarshalJSON_SetTweakChangesTweak(t *testing.T) {
|
|
||||||
pa := &pushrules.PushAction{
|
|
||||||
Action: pushrules.PushActionType("unchanged"),
|
|
||||||
Tweak: pushrules.PushActionTweak("unchanged"),
|
|
||||||
Value: "unchanged",
|
|
||||||
}
|
|
||||||
|
|
||||||
err := pa.UnmarshalJSON([]byte(`{"set_tweak": "foo", "value": 123.0}`))
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, pushrules.ActionSetTweak, pa.Action)
|
|
||||||
assert.Equal(t, pushrules.PushActionTweak("foo"), pa.Tweak)
|
|
||||||
assert.Equal(t, 123.0, pa.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushAction_MarshalJSON_TweakOutputWorks(t *testing.T) {
|
|
||||||
pa := &pushrules.PushAction{
|
|
||||||
Action: pushrules.ActionSetTweak,
|
|
||||||
Tweak: pushrules.PushActionTweak("foo"),
|
|
||||||
Value: "bar",
|
|
||||||
}
|
|
||||||
data, err := pa.MarshalJSON()
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, []byte(`{"set_tweak":"foo","value":"bar"}`), data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushAction_MarshalJSON_OtherOutputWorks(t *testing.T) {
|
|
||||||
pa := &pushrules.PushAction{
|
|
||||||
Action: pushrules.PushActionType("something else"),
|
|
||||||
Tweak: pushrules.PushActionTweak("foo"),
|
|
||||||
Value: "bar",
|
|
||||||
}
|
|
||||||
data, err := pa.MarshalJSON()
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, []byte(`"something else"`), data)
|
|
||||||
}
|
|
@ -1,162 +0,0 @@
|
|||||||
// gomuks - A terminal Matrix client written in Go.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package pushrules
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
|
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
|
||||||
"maunium.net/go/mautrix"
|
|
||||||
|
|
||||||
"maunium.net/go/gomuks/lib/glob"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Room is an interface with the functions that are needed for processing room-specific push conditions
|
|
||||||
type Room interface {
|
|
||||||
GetMember(mxid string) *rooms.Member
|
|
||||||
GetMembers() map[string]*rooms.Member
|
|
||||||
GetSessionOwner() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 Room, event *mautrix.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 Room, event *mautrix.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.String())
|
|
||||||
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.Raw[subkey].(string)
|
|
||||||
return pattern.MatchString(val)
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cond *PushCondition) matchDisplayName(room Room, event *mautrix.Event) bool {
|
|
||||||
ownerID := room.GetSessionOwner()
|
|
||||||
if ownerID == event.Sender {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
member := room.GetMember(ownerID)
|
|
||||||
if member == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := event.Content.Body
|
|
||||||
isAcceptable := func(r uint8) bool {
|
|
||||||
return unicode.IsSpace(rune(r)) || unicode.IsPunct(rune(r))
|
|
||||||
}
|
|
||||||
length := len(member.Displayname)
|
|
||||||
for index := strings.Index(msg, member.Displayname); index != -1; index = strings.Index(msg, member.Displayname) {
|
|
||||||
if (index <= 0 || isAcceptable(msg[index-1])) && (index + length >= len(msg) || isAcceptable(msg[index+length])) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
msg = msg[index+len(member.Displayname):]
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cond *PushCondition) matchMemberCount(room Room, event *mautrix.Event) bool {
|
|
||||||
group := MemberCountFilterRegex.FindStringSubmatch(cond.MemberCountCondition)
|
|
||||||
if len(group) != 3 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
operator := group[1]
|
|
||||||
wantedMemberCount, _ := strconv.Atoi(group[2])
|
|
||||||
|
|
||||||
memberCount := len(room.GetMembers())
|
|
||||||
|
|
||||||
switch operator {
|
|
||||||
case "==", "":
|
|
||||||
return memberCount == wantedMemberCount
|
|
||||||
case ">":
|
|
||||||
return memberCount > wantedMemberCount
|
|
||||||
case ">=":
|
|
||||||
return memberCount >= wantedMemberCount
|
|
||||||
case "<":
|
|
||||||
return memberCount < wantedMemberCount
|
|
||||||
case "<=":
|
|
||||||
return memberCount <= wantedMemberCount
|
|
||||||
default:
|
|
||||||
// Should be impossible due to regex.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,60 +0,0 @@
|
|||||||
// gomuks - A terminal Matrix client written in Go.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package pushrules_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"maunium.net/go/mautrix"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPushCondition_Match_DisplayName(t *testing.T) {
|
|
||||||
event := newFakeEvent(mautrix.EventMessage, mautrix.Content{
|
|
||||||
MsgType: mautrix.MsgText,
|
|
||||||
Body: "tulir: test mention",
|
|
||||||
})
|
|
||||||
event.Sender = "@someone_else:matrix.org"
|
|
||||||
assert.True(t, displaynamePushCondition.Match(displaynameTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushCondition_Match_DisplayName_Fail(t *testing.T) {
|
|
||||||
event := newFakeEvent(mautrix.EventMessage, mautrix.Content{
|
|
||||||
MsgType: mautrix.MsgText,
|
|
||||||
Body: "not a mention",
|
|
||||||
})
|
|
||||||
event.Sender = "@someone_else:matrix.org"
|
|
||||||
assert.False(t, displaynamePushCondition.Match(displaynameTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushCondition_Match_DisplayName_CantHighlightSelf(t *testing.T) {
|
|
||||||
event := newFakeEvent(mautrix.EventMessage, mautrix.Content{
|
|
||||||
MsgType: mautrix.MsgText,
|
|
||||||
Body: "tulir: I can't highlight myself",
|
|
||||||
})
|
|
||||||
assert.False(t, displaynamePushCondition.Match(displaynameTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushCondition_Match_DisplayName_FailsOnEmptyRoom(t *testing.T) {
|
|
||||||
emptyRoom := newFakeRoom(0)
|
|
||||||
event := newFakeEvent(mautrix.EventMessage, mautrix.Content{
|
|
||||||
MsgType: mautrix.MsgText,
|
|
||||||
Body: "tulir: this room doesn't have the owner Member available, so it fails.",
|
|
||||||
})
|
|
||||||
event.Sender = "@someone_else:matrix.org"
|
|
||||||
assert.False(t, displaynamePushCondition.Match(emptyRoom, event))
|
|
||||||
}
|
|
@ -1,96 +0,0 @@
|
|||||||
// gomuks - A terminal Matrix client written in Go.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package pushrules_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"maunium.net/go/mautrix"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPushCondition_Match_KindEvent_MsgType(t *testing.T) {
|
|
||||||
condition := newMatchPushCondition("content.msgtype", "m.emote")
|
|
||||||
event := newFakeEvent(mautrix.EventMessage, mautrix.Content{
|
|
||||||
Raw: map[string]interface{}{
|
|
||||||
"msgtype": "m.emote",
|
|
||||||
"body": "tests gomuks pushconditions",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
assert.True(t, condition.Match(blankTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushCondition_Match_KindEvent_MsgType_Fail(t *testing.T) {
|
|
||||||
condition := newMatchPushCondition("content.msgtype", "m.emote")
|
|
||||||
|
|
||||||
event := newFakeEvent(mautrix.EventMessage, mautrix.Content{
|
|
||||||
Raw: map[string]interface{}{
|
|
||||||
"msgtype": "m.text",
|
|
||||||
"body": "I'm testing gomuks pushconditions",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
assert.False(t, condition.Match(blankTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushCondition_Match_KindEvent_EventType(t *testing.T) {
|
|
||||||
condition := newMatchPushCondition("type", "m.room.foo")
|
|
||||||
event := newFakeEvent(mautrix.NewEventType("m.room.foo"), mautrix.Content{})
|
|
||||||
assert.True(t, condition.Match(blankTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushCondition_Match_KindEvent_EventType_IllegalGlob(t *testing.T) {
|
|
||||||
condition := newMatchPushCondition("type", "m.room.invalid_glo[b")
|
|
||||||
event := newFakeEvent(mautrix.NewEventType("m.room.invalid_glob"), mautrix.Content{})
|
|
||||||
assert.False(t, condition.Match(blankTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushCondition_Match_KindEvent_Sender_Fail(t *testing.T) {
|
|
||||||
condition := newMatchPushCondition("sender", "@foo:maunium.net")
|
|
||||||
event := newFakeEvent(mautrix.NewEventType("m.room.foo"), mautrix.Content{})
|
|
||||||
assert.False(t, condition.Match(blankTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushCondition_Match_KindEvent_RoomID(t *testing.T) {
|
|
||||||
condition := newMatchPushCondition("room_id", "!fakeroom:maunium.net")
|
|
||||||
event := newFakeEvent(mautrix.NewEventType(""), mautrix.Content{})
|
|
||||||
assert.True(t, condition.Match(blankTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushCondition_Match_KindEvent_BlankStateKey(t *testing.T) {
|
|
||||||
condition := newMatchPushCondition("state_key", "")
|
|
||||||
event := newFakeEvent(mautrix.NewEventType("m.room.foo"), mautrix.Content{})
|
|
||||||
assert.True(t, condition.Match(blankTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushCondition_Match_KindEvent_BlankStateKey_Fail(t *testing.T) {
|
|
||||||
condition := newMatchPushCondition("state_key", "not blank")
|
|
||||||
event := newFakeEvent(mautrix.NewEventType("m.room.foo"), mautrix.Content{})
|
|
||||||
assert.False(t, condition.Match(blankTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushCondition_Match_KindEvent_NonBlankStateKey(t *testing.T) {
|
|
||||||
condition := newMatchPushCondition("state_key", "*:maunium.net")
|
|
||||||
event := newFakeEvent(mautrix.NewEventType("m.room.foo"), mautrix.Content{})
|
|
||||||
event.StateKey = &event.Sender
|
|
||||||
assert.True(t, condition.Match(blankTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushCondition_Match_KindEvent_UnknownKey(t *testing.T) {
|
|
||||||
condition := newMatchPushCondition("non-existent key", "doesn't affect anything")
|
|
||||||
event := newFakeEvent(mautrix.NewEventType("m.room.foo"), mautrix.Content{})
|
|
||||||
assert.False(t, condition.Match(blankTestRoom, event))
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
// gomuks - A terminal Matrix client written in Go.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package pushrules_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPushCondition_Match_KindMemberCount_OneToOne_ImplicitPrefix(t *testing.T) {
|
|
||||||
condition := newCountPushCondition("2")
|
|
||||||
room := newFakeRoom(2)
|
|
||||||
assert.True(t, condition.Match(room, countConditionTestEvent))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushCondition_Match_KindMemberCount_OneToOne_ExplicitPrefix(t *testing.T) {
|
|
||||||
condition := newCountPushCondition("==2")
|
|
||||||
room := newFakeRoom(2)
|
|
||||||
assert.True(t, condition.Match(room, countConditionTestEvent))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushCondition_Match_KindMemberCount_BigRoom(t *testing.T) {
|
|
||||||
condition := newCountPushCondition(">200")
|
|
||||||
room := newFakeRoom(201)
|
|
||||||
assert.True(t, condition.Match(room, countConditionTestEvent))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushCondition_Match_KindMemberCount_BigRoom_Fail(t *testing.T) {
|
|
||||||
condition := newCountPushCondition(">=200")
|
|
||||||
room := newFakeRoom(199)
|
|
||||||
assert.False(t, condition.Match(room, countConditionTestEvent))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushCondition_Match_KindMemberCount_SmallRoom(t *testing.T) {
|
|
||||||
condition := newCountPushCondition("<10")
|
|
||||||
room := newFakeRoom(9)
|
|
||||||
assert.True(t, condition.Match(room, countConditionTestEvent))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushCondition_Match_KindMemberCount_SmallRoom_Fail(t *testing.T) {
|
|
||||||
condition := newCountPushCondition("<=10")
|
|
||||||
room := newFakeRoom(11)
|
|
||||||
assert.False(t, condition.Match(room, countConditionTestEvent))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushCondition_Match_KindMemberCount_InvalidPrefix(t *testing.T) {
|
|
||||||
condition := newCountPushCondition("??10")
|
|
||||||
room := newFakeRoom(11)
|
|
||||||
assert.False(t, condition.Match(room, countConditionTestEvent))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushCondition_Match_KindMemberCount_InvalidCondition(t *testing.T) {
|
|
||||||
condition := newCountPushCondition("foobar")
|
|
||||||
room := newFakeRoom(1)
|
|
||||||
assert.False(t, condition.Match(room, countConditionTestEvent))
|
|
||||||
}
|
|
@ -1,132 +0,0 @@
|
|||||||
// gomuks - A terminal Matrix client written in Go.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package pushrules_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"maunium.net/go/mautrix"
|
|
||||||
"maunium.net/go/gomuks/matrix/pushrules"
|
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
blankTestRoom *rooms.Room
|
|
||||||
displaynameTestRoom pushrules.Room
|
|
||||||
|
|
||||||
countConditionTestEvent *mautrix.Event
|
|
||||||
|
|
||||||
displaynamePushCondition *pushrules.PushCondition
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
blankTestRoom = rooms.NewRoom("!fakeroom:maunium.net", "@tulir:maunium.net")
|
|
||||||
|
|
||||||
countConditionTestEvent = &mautrix.Event{
|
|
||||||
Sender: "@tulir:maunium.net",
|
|
||||||
Type: mautrix.EventMessage,
|
|
||||||
Timestamp: 1523791120,
|
|
||||||
ID: "$123:maunium.net",
|
|
||||||
RoomID: "!fakeroom:maunium.net",
|
|
||||||
Content: mautrix.Content{
|
|
||||||
MsgType: mautrix.MsgText,
|
|
||||||
Body: "test",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
displaynameTestRoom = newFakeRoom(4)
|
|
||||||
displaynamePushCondition = &pushrules.PushCondition{
|
|
||||||
Kind: pushrules.KindContainsDisplayName,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFakeEvent(evtType mautrix.EventType, content mautrix.Content) *mautrix.Event {
|
|
||||||
return &mautrix.Event{
|
|
||||||
Sender: "@tulir:maunium.net",
|
|
||||||
Type: evtType,
|
|
||||||
Timestamp: 1523791120,
|
|
||||||
ID: "$123:maunium.net",
|
|
||||||
RoomID: "!fakeroom:maunium.net",
|
|
||||||
Content: content,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newCountPushCondition(condition string) *pushrules.PushCondition {
|
|
||||||
return &pushrules.PushCondition{
|
|
||||||
Kind: pushrules.KindRoomMemberCount,
|
|
||||||
MemberCountCondition: condition,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMatchPushCondition(key, pattern string) *pushrules.PushCondition {
|
|
||||||
return &pushrules.PushCondition{
|
|
||||||
Kind: pushrules.KindEventMatch,
|
|
||||||
Key: key,
|
|
||||||
Pattern: pattern,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushCondition_Match_InvalidKind(t *testing.T) {
|
|
||||||
condition := &pushrules.PushCondition{
|
|
||||||
Kind: pushrules.PushCondKind("invalid"),
|
|
||||||
}
|
|
||||||
event := newFakeEvent(mautrix.EventType{Type: "m.room.foobar"}, mautrix.Content{})
|
|
||||||
assert.False(t, condition.Match(blankTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
type FakeRoom struct {
|
|
||||||
members map[string]*mautrix.Member
|
|
||||||
owner string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFakeRoom(memberCount int) *FakeRoom {
|
|
||||||
room := &FakeRoom{
|
|
||||||
owner: "@tulir:maunium.net",
|
|
||||||
members: make(map[string]*mautrix.Member),
|
|
||||||
}
|
|
||||||
|
|
||||||
if memberCount >= 1 {
|
|
||||||
room.members["@tulir:maunium.net"] = &mautrix.Member{
|
|
||||||
Membership: mautrix.MembershipJoin,
|
|
||||||
Displayname: "tulir",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < memberCount-1; i++ {
|
|
||||||
mxid := fmt.Sprintf("@extrauser_%d:matrix.org", i)
|
|
||||||
room.members[mxid] = &mautrix.Member{
|
|
||||||
Membership: mautrix.MembershipJoin,
|
|
||||||
Displayname: fmt.Sprintf("Extra User %d", i),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return room
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fr *FakeRoom) GetMember(mxid string) *mautrix.Member {
|
|
||||||
return fr.members[mxid]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fr *FakeRoom) GetSessionOwner() string {
|
|
||||||
return fr.owner
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fr *FakeRoom) GetMembers() map[string]*mautrix.Member {
|
|
||||||
return fr.members
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
// Package pushrules contains utilities to parse push notification rules.
|
|
||||||
package pushrules
|
|
@ -1,37 +0,0 @@
|
|||||||
package pushrules
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"maunium.net/go/mautrix"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetPushRules returns the push notification rules for the global scope.
|
|
||||||
func GetPushRules(client *mautrix.Client) (*PushRuleset, error) {
|
|
||||||
return GetScopedPushRules(client, "global")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetScopedPushRules returns the push notification rules for the given scope.
|
|
||||||
func GetScopedPushRules(client *mautrix.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
|
|
||||||
}
|
|
||||||
|
|
||||||
type contentWithRuleset struct {
|
|
||||||
Ruleset *PushRuleset `json:"global"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// EventToPushRules converts a m.push_rules event to a PushRuleset by passing the data through JSON.
|
|
||||||
func EventToPushRules(event *mautrix.Event) (*PushRuleset, error) {
|
|
||||||
content := &contentWithRuleset{}
|
|
||||||
err := json.Unmarshal(event.Content.VeryRaw, content)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return content.Ruleset, nil
|
|
||||||
}
|
|
@ -1,249 +0,0 @@
|
|||||||
// gomuks - A terminal Matrix client written in Go.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package pushrules_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"maunium.net/go/mautrix"
|
|
||||||
"maunium.net/go/gomuks/matrix/pushrules"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEventToPushRules(t *testing.T) {
|
|
||||||
event := &mautrix.Event{
|
|
||||||
Type: mautrix.AccountDataPushRules,
|
|
||||||
Timestamp: 1523380910,
|
|
||||||
Content: mautrix.Content{
|
|
||||||
VeryRaw: json.RawMessage(JSONExamplePushRules),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
pushRuleset, err := pushrules.EventToPushRules(event)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.NotNil(t, pushRuleset)
|
|
||||||
|
|
||||||
assert.IsType(t, pushRuleset.Override, pushrules.PushRuleArray{})
|
|
||||||
assert.IsType(t, pushRuleset.Content, pushrules.PushRuleArray{})
|
|
||||||
assert.IsType(t, pushRuleset.Room, pushrules.PushRuleMap{})
|
|
||||||
assert.IsType(t, pushRuleset.Sender, pushrules.PushRuleMap{})
|
|
||||||
assert.IsType(t, pushRuleset.Underride, pushrules.PushRuleArray{})
|
|
||||||
assert.Len(t, pushRuleset.Override, 2)
|
|
||||||
assert.Len(t, pushRuleset.Content, 1)
|
|
||||||
assert.Empty(t, pushRuleset.Room.Map)
|
|
||||||
assert.Empty(t, pushRuleset.Sender.Map)
|
|
||||||
assert.Len(t, pushRuleset.Underride, 6)
|
|
||||||
|
|
||||||
assert.Len(t, pushRuleset.Content[0].Actions, 3)
|
|
||||||
assert.True(t, pushRuleset.Content[0].Default)
|
|
||||||
assert.True(t, pushRuleset.Content[0].Enabled)
|
|
||||||
assert.Empty(t, pushRuleset.Content[0].Conditions)
|
|
||||||
assert.Equal(t, "alice", pushRuleset.Content[0].Pattern)
|
|
||||||
assert.Equal(t, ".m.rule.contains_user_name", pushRuleset.Content[0].RuleID)
|
|
||||||
|
|
||||||
assert.False(t, pushRuleset.Override[0].Actions.Should().Notify)
|
|
||||||
assert.True(t, pushRuleset.Override[0].Actions.Should().NotifySpecified)
|
|
||||||
}
|
|
||||||
|
|
||||||
const JSONExamplePushRules = `{
|
|
||||||
"global": {
|
|
||||||
"content": [
|
|
||||||
{
|
|
||||||
"actions": [
|
|
||||||
"notify",
|
|
||||||
{
|
|
||||||
"set_tweak": "sound",
|
|
||||||
"value": "default"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"set_tweak": "highlight"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"default": true,
|
|
||||||
"enabled": true,
|
|
||||||
"pattern": "alice",
|
|
||||||
"rule_id": ".m.rule.contains_user_name"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"override": [
|
|
||||||
{
|
|
||||||
"actions": [
|
|
||||||
"dont_notify"
|
|
||||||
],
|
|
||||||
"conditions": [],
|
|
||||||
"default": true,
|
|
||||||
"enabled": false,
|
|
||||||
"rule_id": ".m.rule.master"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"actions": [
|
|
||||||
"dont_notify"
|
|
||||||
],
|
|
||||||
"conditions": [
|
|
||||||
{
|
|
||||||
"key": "content.msgtype",
|
|
||||||
"kind": "event_match",
|
|
||||||
"pattern": "m.notice"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"default": true,
|
|
||||||
"enabled": true,
|
|
||||||
"rule_id": ".m.rule.suppress_notices"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"room": [],
|
|
||||||
"sender": [],
|
|
||||||
"underride": [
|
|
||||||
{
|
|
||||||
"actions": [
|
|
||||||
"notify",
|
|
||||||
{
|
|
||||||
"set_tweak": "sound",
|
|
||||||
"value": "ring"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"set_tweak": "highlight",
|
|
||||||
"value": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"conditions": [
|
|
||||||
{
|
|
||||||
"key": "type",
|
|
||||||
"kind": "event_match",
|
|
||||||
"pattern": "m.call.invite"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"default": true,
|
|
||||||
"enabled": true,
|
|
||||||
"rule_id": ".m.rule.call"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"actions": [
|
|
||||||
"notify",
|
|
||||||
{
|
|
||||||
"set_tweak": "sound",
|
|
||||||
"value": "default"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"set_tweak": "highlight"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"conditions": [
|
|
||||||
{
|
|
||||||
"kind": "contains_display_name"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"default": true,
|
|
||||||
"enabled": true,
|
|
||||||
"rule_id": ".m.rule.contains_display_name"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"actions": [
|
|
||||||
"notify",
|
|
||||||
{
|
|
||||||
"set_tweak": "sound",
|
|
||||||
"value": "default"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"set_tweak": "highlight",
|
|
||||||
"value": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"conditions": [
|
|
||||||
{
|
|
||||||
"is": "2",
|
|
||||||
"kind": "room_member_count"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"default": true,
|
|
||||||
"enabled": true,
|
|
||||||
"rule_id": ".m.rule.room_one_to_one"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"actions": [
|
|
||||||
"notify",
|
|
||||||
{
|
|
||||||
"set_tweak": "sound",
|
|
||||||
"value": "default"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"set_tweak": "highlight",
|
|
||||||
"value": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"conditions": [
|
|
||||||
{
|
|
||||||
"key": "type",
|
|
||||||
"kind": "event_match",
|
|
||||||
"pattern": "m.room.member"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "content.membership",
|
|
||||||
"kind": "event_match",
|
|
||||||
"pattern": "invite"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "state_key",
|
|
||||||
"kind": "event_match",
|
|
||||||
"pattern": "@alice:example.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"default": true,
|
|
||||||
"enabled": true,
|
|
||||||
"rule_id": ".m.rule.invite_for_me"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"actions": [
|
|
||||||
"notify",
|
|
||||||
{
|
|
||||||
"set_tweak": "highlight",
|
|
||||||
"value": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"conditions": [
|
|
||||||
{
|
|
||||||
"key": "type",
|
|
||||||
"kind": "event_match",
|
|
||||||
"pattern": "m.room.member"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"default": true,
|
|
||||||
"enabled": true,
|
|
||||||
"rule_id": ".m.rule.member_event"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"actions": [
|
|
||||||
"notify",
|
|
||||||
{
|
|
||||||
"set_tweak": "highlight",
|
|
||||||
"value": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"conditions": [
|
|
||||||
{
|
|
||||||
"key": "type",
|
|
||||||
"kind": "event_match",
|
|
||||||
"pattern": "m.room.message"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"default": true,
|
|
||||||
"enabled": true,
|
|
||||||
"rule_id": ".m.rule.message"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}`
|
|
@ -1,160 +0,0 @@
|
|||||||
// gomuks - A terminal Matrix client written in Go.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package pushrules
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/gob"
|
|
||||||
|
|
||||||
"maunium.net/go/mautrix"
|
|
||||||
|
|
||||||
"maunium.net/go/gomuks/lib/glob"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
gob.Register(PushRuleArray{})
|
|
||||||
gob.Register(PushRuleMap{})
|
|
||||||
}
|
|
||||||
|
|
||||||
type PushRuleCollection interface {
|
|
||||||
GetActions(room Room, event *mautrix.Event) PushActionArray
|
|
||||||
}
|
|
||||||
|
|
||||||
type PushRuleArray []*PushRule
|
|
||||||
|
|
||||||
func (rules PushRuleArray) SetType(typ PushRuleType) PushRuleArray {
|
|
||||||
for _, rule := range rules {
|
|
||||||
rule.Type = typ
|
|
||||||
}
|
|
||||||
return rules
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rules PushRuleArray) GetActions(room Room, event *mautrix.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 Room, event *mautrix.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 Room, event *mautrix.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 Room, event *mautrix.Event) bool {
|
|
||||||
for _, cond := range rule.Conditions {
|
|
||||||
if !cond.Match(room, event) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rule *PushRule) matchPattern(room Room, event *mautrix.Event) bool {
|
|
||||||
pattern, err := glob.Compile(rule.Pattern)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return pattern.MatchString(event.Content.Body)
|
|
||||||
}
|
|
@ -1,294 +0,0 @@
|
|||||||
// gomuks - A terminal Matrix client written in Go.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package pushrules_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"maunium.net/go/gomuks/matrix/pushrules"
|
|
||||||
"maunium.net/go/mautrix"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPushRuleArray_GetActions_FirstMatchReturns(t *testing.T) {
|
|
||||||
cond1 := newMatchPushCondition("content.msgtype", "m.emote")
|
|
||||||
cond2 := newMatchPushCondition("content.body", "no match")
|
|
||||||
actions1 := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionNotify},
|
|
||||||
{Action: pushrules.ActionSetTweak, Tweak: pushrules.TweakHighlight},
|
|
||||||
{Action: pushrules.ActionSetTweak, Tweak: pushrules.TweakSound, Value: "ping"},
|
|
||||||
}
|
|
||||||
rule1 := &pushrules.PushRule{
|
|
||||||
Type: pushrules.OverrideRule,
|
|
||||||
Enabled: true,
|
|
||||||
Conditions: []*pushrules.PushCondition{cond1, cond2},
|
|
||||||
Actions: actions1,
|
|
||||||
}
|
|
||||||
|
|
||||||
actions2 := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionDontNotify},
|
|
||||||
{Action: pushrules.ActionSetTweak, Tweak: pushrules.TweakHighlight, Value: false},
|
|
||||||
{Action: pushrules.ActionSetTweak, Tweak: pushrules.TweakSound, Value: "pong"},
|
|
||||||
}
|
|
||||||
rule2 := &pushrules.PushRule{
|
|
||||||
Type: pushrules.RoomRule,
|
|
||||||
Enabled: true,
|
|
||||||
RuleID: "!fakeroom:maunium.net",
|
|
||||||
Actions: actions2,
|
|
||||||
}
|
|
||||||
|
|
||||||
actions3 := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionCoalesce},
|
|
||||||
}
|
|
||||||
rule3 := &pushrules.PushRule{
|
|
||||||
Type: pushrules.SenderRule,
|
|
||||||
Enabled: true,
|
|
||||||
RuleID: "@tulir:maunium.net",
|
|
||||||
Actions: actions3,
|
|
||||||
}
|
|
||||||
|
|
||||||
rules := pushrules.PushRuleArray{rule1, rule2, rule3}
|
|
||||||
|
|
||||||
event := newFakeEvent(mautrix.EventMessage, mautrix.Content{
|
|
||||||
MsgType: mautrix.MsgEmote,
|
|
||||||
Body: "is testing pushrules",
|
|
||||||
})
|
|
||||||
assert.Equal(t, rules.GetActions(blankTestRoom, event), actions2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushRuleArray_GetActions_NoMatchesIsNil(t *testing.T) {
|
|
||||||
cond1 := newMatchPushCondition("content.msgtype", "m.emote")
|
|
||||||
cond2 := newMatchPushCondition("content.body", "no match")
|
|
||||||
actions1 := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionNotify},
|
|
||||||
{Action: pushrules.ActionSetTweak, Tweak: pushrules.TweakHighlight},
|
|
||||||
{Action: pushrules.ActionSetTweak, Tweak: pushrules.TweakSound, Value: "ping"},
|
|
||||||
}
|
|
||||||
rule1 := &pushrules.PushRule{
|
|
||||||
Type: pushrules.OverrideRule,
|
|
||||||
Enabled: true,
|
|
||||||
Conditions: []*pushrules.PushCondition{cond1, cond2},
|
|
||||||
Actions: actions1,
|
|
||||||
}
|
|
||||||
|
|
||||||
actions2 := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionDontNotify},
|
|
||||||
{Action: pushrules.ActionSetTweak, Tweak: pushrules.TweakHighlight, Value: false},
|
|
||||||
{Action: pushrules.ActionSetTweak, Tweak: pushrules.TweakSound, Value: "pong"},
|
|
||||||
}
|
|
||||||
rule2 := &pushrules.PushRule{
|
|
||||||
Type: pushrules.RoomRule,
|
|
||||||
Enabled: true,
|
|
||||||
RuleID: "!realroom:maunium.net",
|
|
||||||
Actions: actions2,
|
|
||||||
}
|
|
||||||
|
|
||||||
actions3 := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionCoalesce},
|
|
||||||
}
|
|
||||||
rule3 := &pushrules.PushRule{
|
|
||||||
Type: pushrules.SenderRule,
|
|
||||||
Enabled: true,
|
|
||||||
RuleID: "@otheruser:maunium.net",
|
|
||||||
Actions: actions3,
|
|
||||||
}
|
|
||||||
|
|
||||||
rules := pushrules.PushRuleArray{rule1, rule2, rule3}
|
|
||||||
|
|
||||||
event := newFakeEvent(mautrix.EventMessage, mautrix.Content{
|
|
||||||
MsgType: mautrix.MsgEmote,
|
|
||||||
Body: "is testing pushrules",
|
|
||||||
})
|
|
||||||
assert.Nil(t, rules.GetActions(blankTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushRuleMap_GetActions_RoomRuleExists(t *testing.T) {
|
|
||||||
actions1 := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionDontNotify},
|
|
||||||
{Action: pushrules.ActionSetTweak, Tweak: pushrules.TweakHighlight, Value: false},
|
|
||||||
{Action: pushrules.ActionSetTweak, Tweak: pushrules.TweakSound, Value: "pong"},
|
|
||||||
}
|
|
||||||
rule1 := &pushrules.PushRule{
|
|
||||||
Type: pushrules.RoomRule,
|
|
||||||
Enabled: true,
|
|
||||||
RuleID: "!realroom:maunium.net",
|
|
||||||
Actions: actions1,
|
|
||||||
}
|
|
||||||
|
|
||||||
actions2 := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionNotify},
|
|
||||||
}
|
|
||||||
rule2 := &pushrules.PushRule{
|
|
||||||
Type: pushrules.RoomRule,
|
|
||||||
Enabled: true,
|
|
||||||
RuleID: "!thirdroom:maunium.net",
|
|
||||||
Actions: actions2,
|
|
||||||
}
|
|
||||||
|
|
||||||
actions3 := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionCoalesce},
|
|
||||||
}
|
|
||||||
rule3 := &pushrules.PushRule{
|
|
||||||
Type: pushrules.RoomRule,
|
|
||||||
Enabled: true,
|
|
||||||
RuleID: "!fakeroom:maunium.net",
|
|
||||||
Actions: actions3,
|
|
||||||
}
|
|
||||||
|
|
||||||
rules := pushrules.PushRuleMap{
|
|
||||||
Map: map[string]*pushrules.PushRule{
|
|
||||||
rule1.RuleID: rule1,
|
|
||||||
rule2.RuleID: rule2,
|
|
||||||
rule3.RuleID: rule3,
|
|
||||||
},
|
|
||||||
Type: pushrules.RoomRule,
|
|
||||||
}
|
|
||||||
|
|
||||||
event := newFakeEvent(mautrix.EventMessage, mautrix.Content{
|
|
||||||
MsgType: mautrix.MsgEmote,
|
|
||||||
Body: "is testing pushrules",
|
|
||||||
})
|
|
||||||
assert.Equal(t, rules.GetActions(blankTestRoom, event), actions3)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushRuleMap_GetActions_RoomRuleDoesntExist(t *testing.T) {
|
|
||||||
actions1 := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionDontNotify},
|
|
||||||
{Action: pushrules.ActionSetTweak, Tweak: pushrules.TweakHighlight, Value: false},
|
|
||||||
{Action: pushrules.ActionSetTweak, Tweak: pushrules.TweakSound, Value: "pong"},
|
|
||||||
}
|
|
||||||
rule1 := &pushrules.PushRule{
|
|
||||||
Type: pushrules.RoomRule,
|
|
||||||
Enabled: true,
|
|
||||||
RuleID: "!realroom:maunium.net",
|
|
||||||
Actions: actions1,
|
|
||||||
}
|
|
||||||
|
|
||||||
actions2 := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionNotify},
|
|
||||||
}
|
|
||||||
rule2 := &pushrules.PushRule{
|
|
||||||
Type: pushrules.RoomRule,
|
|
||||||
Enabled: true,
|
|
||||||
RuleID: "!thirdroom:maunium.net",
|
|
||||||
Actions: actions2,
|
|
||||||
}
|
|
||||||
|
|
||||||
rules := pushrules.PushRuleMap{
|
|
||||||
Map: map[string]*pushrules.PushRule{
|
|
||||||
rule1.RuleID: rule1,
|
|
||||||
rule2.RuleID: rule2,
|
|
||||||
},
|
|
||||||
Type: pushrules.RoomRule,
|
|
||||||
}
|
|
||||||
|
|
||||||
event := newFakeEvent(mautrix.EventMessage, mautrix.Content{
|
|
||||||
MsgType: mautrix.MsgEmote,
|
|
||||||
Body: "is testing pushrules",
|
|
||||||
})
|
|
||||||
assert.Nil(t, rules.GetActions(blankTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushRuleMap_GetActions_SenderRuleExists(t *testing.T) {
|
|
||||||
actions1 := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionDontNotify},
|
|
||||||
{Action: pushrules.ActionSetTweak, Tweak: pushrules.TweakHighlight, Value: false},
|
|
||||||
{Action: pushrules.ActionSetTweak, Tweak: pushrules.TweakSound, Value: "pong"},
|
|
||||||
}
|
|
||||||
rule1 := &pushrules.PushRule{
|
|
||||||
Type: pushrules.SenderRule,
|
|
||||||
Enabled: true,
|
|
||||||
RuleID: "@tulir:maunium.net",
|
|
||||||
Actions: actions1,
|
|
||||||
}
|
|
||||||
|
|
||||||
actions2 := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionNotify},
|
|
||||||
}
|
|
||||||
rule2 := &pushrules.PushRule{
|
|
||||||
Type: pushrules.SenderRule,
|
|
||||||
Enabled: true,
|
|
||||||
RuleID: "@someone:maunium.net",
|
|
||||||
Actions: actions2,
|
|
||||||
}
|
|
||||||
|
|
||||||
actions3 := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionCoalesce},
|
|
||||||
}
|
|
||||||
rule3 := &pushrules.PushRule{
|
|
||||||
Type: pushrules.SenderRule,
|
|
||||||
Enabled: true,
|
|
||||||
RuleID: "@otheruser:matrix.org",
|
|
||||||
Actions: actions3,
|
|
||||||
}
|
|
||||||
|
|
||||||
rules := pushrules.PushRuleMap{
|
|
||||||
Map: map[string]*pushrules.PushRule{
|
|
||||||
rule1.RuleID: rule1,
|
|
||||||
rule2.RuleID: rule2,
|
|
||||||
rule3.RuleID: rule3,
|
|
||||||
},
|
|
||||||
Type: pushrules.SenderRule,
|
|
||||||
}
|
|
||||||
|
|
||||||
event := newFakeEvent(mautrix.EventMessage, mautrix.Content{
|
|
||||||
MsgType: mautrix.MsgEmote,
|
|
||||||
Body: "is testing pushrules",
|
|
||||||
})
|
|
||||||
assert.Equal(t, rules.GetActions(blankTestRoom, event), actions1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushRuleArray_SetTypeAndMap(t *testing.T) {
|
|
||||||
actions1 := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionDontNotify},
|
|
||||||
{Action: pushrules.ActionSetTweak, Tweak: pushrules.TweakHighlight, Value: false},
|
|
||||||
{Action: pushrules.ActionSetTweak, Tweak: pushrules.TweakSound, Value: "pong"},
|
|
||||||
}
|
|
||||||
rule1 := &pushrules.PushRule{
|
|
||||||
Enabled: true,
|
|
||||||
RuleID: "@tulir:maunium.net",
|
|
||||||
Actions: actions1,
|
|
||||||
}
|
|
||||||
|
|
||||||
actions2 := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionNotify},
|
|
||||||
}
|
|
||||||
rule2 := &pushrules.PushRule{
|
|
||||||
Enabled: true,
|
|
||||||
RuleID: "@someone:maunium.net",
|
|
||||||
Actions: actions2,
|
|
||||||
}
|
|
||||||
|
|
||||||
actions3 := pushrules.PushActionArray{
|
|
||||||
{Action: pushrules.ActionCoalesce},
|
|
||||||
}
|
|
||||||
rule3 := &pushrules.PushRule{
|
|
||||||
Enabled: true,
|
|
||||||
RuleID: "@otheruser:matrix.org",
|
|
||||||
Actions: actions3,
|
|
||||||
}
|
|
||||||
|
|
||||||
ruleArray := pushrules.PushRuleArray{rule1, rule2, rule3}
|
|
||||||
ruleMap := ruleArray.SetTypeAndMap(pushrules.SenderRule)
|
|
||||||
assert.Equal(t, pushrules.SenderRule, ruleMap.Type)
|
|
||||||
for _, rule := range ruleArray {
|
|
||||||
assert.Equal(t, rule, ruleMap.Map[rule.RuleID])
|
|
||||||
}
|
|
||||||
newRuleArray := ruleMap.Unmap()
|
|
||||||
for _, rule := range ruleArray {
|
|
||||||
assert.Contains(t, newRuleArray, rule)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,195 +0,0 @@
|
|||||||
// gomuks - A terminal Matrix client written in Go.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package pushrules_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"maunium.net/go/gomuks/matrix/pushrules"
|
|
||||||
"maunium.net/go/mautrix"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPushRule_Match_Conditions(t *testing.T) {
|
|
||||||
cond1 := newMatchPushCondition("content.msgtype", "m.emote")
|
|
||||||
cond2 := newMatchPushCondition("content.body", "*pushrules")
|
|
||||||
rule := &pushrules.PushRule{
|
|
||||||
Type: pushrules.OverrideRule,
|
|
||||||
Enabled: true,
|
|
||||||
Conditions: []*pushrules.PushCondition{cond1, cond2},
|
|
||||||
}
|
|
||||||
|
|
||||||
event := newFakeEvent(mautrix.EventMessage, mautrix.Content{
|
|
||||||
Raw: map[string]interface{}{
|
|
||||||
"msgtype": "m.emote",
|
|
||||||
"body": "is testing pushrules",
|
|
||||||
},
|
|
||||||
MsgType: mautrix.MsgEmote,
|
|
||||||
Body: "is testing pushrules",
|
|
||||||
})
|
|
||||||
assert.True(t, rule.Match(blankTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushRule_Match_Conditions_Disabled(t *testing.T) {
|
|
||||||
cond1 := newMatchPushCondition("content.msgtype", "m.emote")
|
|
||||||
cond2 := newMatchPushCondition("content.body", "*pushrules")
|
|
||||||
rule := &pushrules.PushRule{
|
|
||||||
Type: pushrules.OverrideRule,
|
|
||||||
Enabled: false,
|
|
||||||
Conditions: []*pushrules.PushCondition{cond1, cond2},
|
|
||||||
}
|
|
||||||
|
|
||||||
event := newFakeEvent(mautrix.EventMessage, mautrix.Content{
|
|
||||||
Raw: map[string]interface{}{
|
|
||||||
"msgtype": "m.emote",
|
|
||||||
"body": "is testing pushrules",
|
|
||||||
},
|
|
||||||
MsgType: mautrix.MsgEmote,
|
|
||||||
Body: "is testing pushrules",
|
|
||||||
})
|
|
||||||
assert.False(t, rule.Match(blankTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushRule_Match_Conditions_FailIfOneFails(t *testing.T) {
|
|
||||||
cond1 := newMatchPushCondition("content.msgtype", "m.emote")
|
|
||||||
cond2 := newMatchPushCondition("content.body", "*pushrules")
|
|
||||||
rule := &pushrules.PushRule{
|
|
||||||
Type: pushrules.OverrideRule,
|
|
||||||
Enabled: true,
|
|
||||||
Conditions: []*pushrules.PushCondition{cond1, cond2},
|
|
||||||
}
|
|
||||||
|
|
||||||
event := newFakeEvent(mautrix.EventMessage, mautrix.Content{
|
|
||||||
Raw: map[string]interface{}{
|
|
||||||
"msgtype": "m.text",
|
|
||||||
"body": "I'm testing pushrules",
|
|
||||||
},
|
|
||||||
MsgType: mautrix.MsgText,
|
|
||||||
Body: "I'm testing pushrules",
|
|
||||||
})
|
|
||||||
assert.False(t, rule.Match(blankTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushRule_Match_Content(t *testing.T) {
|
|
||||||
rule := &pushrules.PushRule{
|
|
||||||
Type: pushrules.ContentRule,
|
|
||||||
Enabled: true,
|
|
||||||
Pattern: "is testing*",
|
|
||||||
}
|
|
||||||
|
|
||||||
event := newFakeEvent(mautrix.EventMessage, mautrix.Content{
|
|
||||||
MsgType: mautrix.MsgEmote,
|
|
||||||
Body: "is testing pushrules",
|
|
||||||
})
|
|
||||||
assert.True(t, rule.Match(blankTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushRule_Match_Content_Fail(t *testing.T) {
|
|
||||||
rule := &pushrules.PushRule{
|
|
||||||
Type: pushrules.ContentRule,
|
|
||||||
Enabled: true,
|
|
||||||
Pattern: "is testing*",
|
|
||||||
}
|
|
||||||
|
|
||||||
event := newFakeEvent(mautrix.EventMessage, mautrix.Content{
|
|
||||||
MsgType: mautrix.MsgEmote,
|
|
||||||
Body: "is not testing pushrules",
|
|
||||||
})
|
|
||||||
assert.False(t, rule.Match(blankTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushRule_Match_Content_ImplicitGlob(t *testing.T) {
|
|
||||||
rule := &pushrules.PushRule{
|
|
||||||
Type: pushrules.ContentRule,
|
|
||||||
Enabled: true,
|
|
||||||
Pattern: "testing",
|
|
||||||
}
|
|
||||||
|
|
||||||
event := newFakeEvent(mautrix.EventMessage, mautrix.Content{
|
|
||||||
MsgType: mautrix.MsgEmote,
|
|
||||||
Body: "is not testing pushrules",
|
|
||||||
})
|
|
||||||
assert.True(t, rule.Match(blankTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushRule_Match_Content_IllegalGlob(t *testing.T) {
|
|
||||||
rule := &pushrules.PushRule{
|
|
||||||
Type: pushrules.ContentRule,
|
|
||||||
Enabled: true,
|
|
||||||
Pattern: "this is not a valid glo[b",
|
|
||||||
}
|
|
||||||
|
|
||||||
event := newFakeEvent(mautrix.EventMessage, mautrix.Content{
|
|
||||||
MsgType: mautrix.MsgEmote,
|
|
||||||
Body: "this is not a valid glob",
|
|
||||||
})
|
|
||||||
assert.False(t, rule.Match(blankTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushRule_Match_Room(t *testing.T) {
|
|
||||||
rule := &pushrules.PushRule{
|
|
||||||
Type: pushrules.RoomRule,
|
|
||||||
Enabled: true,
|
|
||||||
RuleID: "!fakeroom:maunium.net",
|
|
||||||
}
|
|
||||||
|
|
||||||
event := newFakeEvent(mautrix.EventMessage, mautrix.Content{})
|
|
||||||
assert.True(t, rule.Match(blankTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushRule_Match_Room_Fail(t *testing.T) {
|
|
||||||
rule := &pushrules.PushRule{
|
|
||||||
Type: pushrules.RoomRule,
|
|
||||||
Enabled: true,
|
|
||||||
RuleID: "!otherroom:maunium.net",
|
|
||||||
}
|
|
||||||
|
|
||||||
event := newFakeEvent(mautrix.EventMessage, mautrix.Content{})
|
|
||||||
assert.False(t, rule.Match(blankTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushRule_Match_Sender(t *testing.T) {
|
|
||||||
rule := &pushrules.PushRule{
|
|
||||||
Type: pushrules.SenderRule,
|
|
||||||
Enabled: true,
|
|
||||||
RuleID: "@tulir:maunium.net",
|
|
||||||
}
|
|
||||||
|
|
||||||
event := newFakeEvent(mautrix.EventMessage, mautrix.Content{})
|
|
||||||
assert.True(t, rule.Match(blankTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushRule_Match_Sender_Fail(t *testing.T) {
|
|
||||||
rule := &pushrules.PushRule{
|
|
||||||
Type: pushrules.RoomRule,
|
|
||||||
Enabled: true,
|
|
||||||
RuleID: "@someone:matrix.org",
|
|
||||||
}
|
|
||||||
|
|
||||||
event := newFakeEvent(mautrix.EventMessage, mautrix.Content{})
|
|
||||||
assert.False(t, rule.Match(blankTestRoom, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushRule_Match_UnknownTypeAlwaysFail(t *testing.T) {
|
|
||||||
rule := &pushrules.PushRule{
|
|
||||||
Type: pushrules.PushRuleType("foobar"),
|
|
||||||
Enabled: true,
|
|
||||||
RuleID: "@someone:matrix.org",
|
|
||||||
}
|
|
||||||
|
|
||||||
event := newFakeEvent(mautrix.EventMessage, mautrix.Content{})
|
|
||||||
assert.False(t, rule.Match(blankTestRoom, event))
|
|
||||||
}
|
|
@ -1,98 +0,0 @@
|
|||||||
// gomuks - A terminal Matrix client written in Go.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package pushrules
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"maunium.net/go/mautrix"
|
|
||||||
)
|
|
||||||
|
|
||||||
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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON parses JSON into this PushRuleset.
|
|
||||||
//
|
|
||||||
// For override, sender and underride push rule arrays, the type is added
|
|
||||||
// to each PushRule and the array is used as-is.
|
|
||||||
//
|
|
||||||
// For room and sender push rule arrays, the type is added to each PushRule
|
|
||||||
// and the array is converted to a map with the rule ID as the key and the
|
|
||||||
// PushRule as the value.
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON is the reverse of UnmarshalJSON()
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultPushActions is the value returned if none of the rule
|
|
||||||
// collections in a Ruleset match the event given to GetActions()
|
|
||||||
var DefaultPushActions = PushActionArray{&PushAction{Action: ActionDontNotify}}
|
|
||||||
|
|
||||||
// GetActions matches the given event against all of the push rule
|
|
||||||
// collections in this push ruleset in the order of priority as
|
|
||||||
// specified in spec section 11.12.1.4.
|
|
||||||
func (rs *PushRuleset) GetActions(room Room, event *mautrix.Event) (match PushActionArray) {
|
|
||||||
// Add push rule collections to array in priority order
|
|
||||||
arrays := []PushRuleCollection{rs.Override, rs.Content, rs.Room, rs.Sender, rs.Underride}
|
|
||||||
// Loop until one of the push rule collections matches the room/event combo.
|
|
||||||
for _, pra := range arrays {
|
|
||||||
if pra == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if match = pra.GetActions(room, event); match != nil {
|
|
||||||
// Match found, return it.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// No match found, return default actions.
|
|
||||||
return DefaultPushActions
|
|
||||||
}
|
|
@ -27,6 +27,8 @@ import (
|
|||||||
sync "github.com/sasha-s/go-deadlock"
|
sync "github.com/sasha-s/go-deadlock"
|
||||||
|
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
|
"maunium.net/go/mautrix/event"
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/debug"
|
"maunium.net/go/gomuks/debug"
|
||||||
)
|
)
|
||||||
@ -54,25 +56,27 @@ type RoomTag struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UnreadMessage struct {
|
type UnreadMessage struct {
|
||||||
EventID string
|
EventID id.EventID
|
||||||
Counted bool
|
Counted bool
|
||||||
Highlight bool
|
Highlight bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Member struct {
|
type Member struct {
|
||||||
mautrix.Member
|
event.Member
|
||||||
|
|
||||||
// The user who sent the membership event
|
// The user who sent the membership event
|
||||||
Sender string `json:"-"`
|
Sender id.UserID `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Room represents a single Matrix room.
|
// Room represents a single Matrix room.
|
||||||
type Room struct {
|
type Room struct {
|
||||||
// The room ID.
|
// The room ID.
|
||||||
ID string
|
ID id.RoomID
|
||||||
|
|
||||||
// Whether or not the user has left the room.
|
// Whether or not the user has left the room.
|
||||||
HasLeft bool
|
HasLeft bool
|
||||||
|
// Whether or not the room is encrypted.
|
||||||
|
Encrypted bool
|
||||||
|
|
||||||
// The first batch of events that has been fetched for this room.
|
// The first batch of events that has been fetched for this room.
|
||||||
// Used for fetching additional history.
|
// Used for fetching additional history.
|
||||||
@ -80,14 +84,14 @@ type Room struct {
|
|||||||
// The last_batch field from the most recent sync. Used for fetching member lists.
|
// The last_batch field from the most recent sync. Used for fetching member lists.
|
||||||
LastPrevBatch string
|
LastPrevBatch string
|
||||||
// The MXID of the user whose session this room was created for.
|
// The MXID of the user whose session this room was created for.
|
||||||
SessionUserID string
|
SessionUserID id.UserID
|
||||||
SessionMember *Member
|
SessionMember *Member
|
||||||
|
|
||||||
// The number of unread messages that were notified about.
|
// The number of unread messages that were notified about.
|
||||||
UnreadMessages []UnreadMessage
|
UnreadMessages []UnreadMessage
|
||||||
unreadCountCache *int
|
unreadCountCache *int
|
||||||
highlightCache *bool
|
highlightCache *bool
|
||||||
lastMarkedRead string
|
lastMarkedRead id.EventID
|
||||||
// Whether or not this room is marked as a direct chat.
|
// Whether or not this room is marked as a direct chat.
|
||||||
IsDirect bool
|
IsDirect bool
|
||||||
|
|
||||||
@ -101,10 +105,10 @@ type Room struct {
|
|||||||
// Whether or not the members for this room have been fetched from the server.
|
// Whether or not the members for this room have been fetched from the server.
|
||||||
MembersFetched bool
|
MembersFetched bool
|
||||||
// Room state cache.
|
// Room state cache.
|
||||||
state map[mautrix.EventType]map[string]*mautrix.Event
|
state map[event.Type]map[string]*event.Event
|
||||||
// MXID -> Member cache calculated from membership events.
|
// MXID -> Member cache calculated from membership events.
|
||||||
memberCache map[string]*Member
|
memberCache map[id.UserID]*Member
|
||||||
exMemberCache map[string]*Member
|
exMemberCache map[id.UserID]*Member
|
||||||
// The first two non-SessionUserID members in the room. Calculated at
|
// The first two non-SessionUserID members in the room. Calculated at
|
||||||
// the same time as memberCache.
|
// the same time as memberCache.
|
||||||
firstMemberCache *Member
|
firstMemberCache *Member
|
||||||
@ -117,11 +121,11 @@ type Room struct {
|
|||||||
// The topic of the room. Directly fetched from the m.room.topic state event.
|
// The topic of the room. Directly fetched from the m.room.topic state event.
|
||||||
topicCache string
|
topicCache string
|
||||||
// The canonical alias of the room. Directly fetched from the m.room.canonical_alias state event.
|
// The canonical alias of the room. Directly fetched from the m.room.canonical_alias state event.
|
||||||
CanonicalAliasCache string
|
CanonicalAliasCache id.RoomAlias
|
||||||
// Whether or not the room has been tombstoned.
|
// Whether or not the room has been tombstoned.
|
||||||
replacedCache bool
|
replacedCache bool
|
||||||
// The room ID that replaced this room.
|
// The room ID that replaced this room.
|
||||||
replacedByCache *string
|
replacedByCache *id.RoomID
|
||||||
|
|
||||||
// Path for state store file.
|
// Path for state store file.
|
||||||
path string
|
path string
|
||||||
@ -174,7 +178,7 @@ func (room *Room) load() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
debug.Print("Loading state for room", room.ID, "from disk")
|
debug.Print("Loading state for room", room.ID, "from disk")
|
||||||
room.state = make(map[mautrix.EventType]map[string]*mautrix.Event)
|
room.state = make(map[event.Type]map[string]*event.Event)
|
||||||
file, err := os.OpenFile(room.path, os.O_RDONLY, 0600)
|
file, err := os.OpenFile(room.path, os.O_RDONLY, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
@ -265,7 +269,7 @@ func (room *Room) Save() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MarkRead clears the new message statuses on this room.
|
// MarkRead clears the new message statuses on this room.
|
||||||
func (room *Room) MarkRead(eventID string) bool {
|
func (room *Room) MarkRead(eventID id.EventID) bool {
|
||||||
room.lock.Lock()
|
room.lock.Lock()
|
||||||
defer room.lock.Unlock()
|
defer room.lock.Unlock()
|
||||||
if room.lastMarkedRead == eventID {
|
if room.lastMarkedRead == eventID {
|
||||||
@ -319,7 +323,7 @@ func (room *Room) HasNewMessages() bool {
|
|||||||
return len(room.UnreadMessages) > 0
|
return len(room.UnreadMessages) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (room *Room) AddUnread(eventID string, counted, highlight bool) {
|
func (room *Room) AddUnread(eventID id.EventID, counted, highlight bool) {
|
||||||
room.lock.Lock()
|
room.lock.Lock()
|
||||||
defer room.lock.Unlock()
|
defer room.lock.Unlock()
|
||||||
room.UnreadMessages = append(room.UnreadMessages, UnreadMessage{
|
room.UnreadMessages = append(room.UnreadMessages, UnreadMessage{
|
||||||
@ -341,18 +345,25 @@ func (room *Room) AddUnread(eventID string, counted, highlight bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
tagDirect = RoomTag{"net.maunium.gomuks.fake.direct", "0.5"}
|
||||||
|
tagInvite = RoomTag{"net.maunium.gomuks.fake.invite", "0.5"}
|
||||||
|
tagDefault = RoomTag{"", "0.5"}
|
||||||
|
tagLeave = RoomTag{"net.maunium.gomuks.fake.leave", "0.5"}
|
||||||
|
)
|
||||||
|
|
||||||
func (room *Room) Tags() []RoomTag {
|
func (room *Room) Tags() []RoomTag {
|
||||||
room.lock.RLock()
|
room.lock.RLock()
|
||||||
defer room.lock.RUnlock()
|
defer room.lock.RUnlock()
|
||||||
if len(room.RawTags) == 0 {
|
if len(room.RawTags) == 0 {
|
||||||
if room.IsDirect {
|
if room.IsDirect {
|
||||||
return []RoomTag{{"net.maunium.gomuks.fake.direct", "0.5"}}
|
return []RoomTag{tagDirect}
|
||||||
} else if room.SessionMember != nil && room.SessionMember.Membership == mautrix.MembershipInvite {
|
} else if room.SessionMember != nil && room.SessionMember.Membership == event.MembershipInvite {
|
||||||
return []RoomTag{{"net.maunium.gomuks.fake.invite", "0.5"}}
|
return []RoomTag{tagInvite}
|
||||||
} else if room.SessionMember != nil && room.SessionMember.Membership != mautrix.MembershipJoin {
|
} else if room.SessionMember != nil && room.SessionMember.Membership != event.MembershipJoin {
|
||||||
return []RoomTag{{"net.maunium.gomuks.fake.leave", "0.5"}}
|
return []RoomTag{tagLeave}
|
||||||
}
|
}
|
||||||
return []RoomTag{{"", "0.5"}}
|
return []RoomTag{tagDefault}
|
||||||
}
|
}
|
||||||
return room.RawTags
|
return room.RawTags
|
||||||
}
|
}
|
||||||
@ -374,46 +385,46 @@ func (room *Room) UpdateSummary(summary mautrix.LazyLoadSummary) {
|
|||||||
|
|
||||||
// UpdateState updates the room's current state with the given Event. This will clobber events based
|
// UpdateState updates the room's current state with the given Event. This will clobber events based
|
||||||
// on the type/state_key combination.
|
// on the type/state_key combination.
|
||||||
func (room *Room) UpdateState(event *mautrix.Event) {
|
func (room *Room) UpdateState(evt *event.Event) {
|
||||||
if event.StateKey == nil {
|
if evt.StateKey == nil {
|
||||||
panic("Tried to UpdateState() event with no state key.")
|
panic("Tried to UpdateState() event with no state key.")
|
||||||
}
|
}
|
||||||
room.Load()
|
room.Load()
|
||||||
room.lock.Lock()
|
room.lock.Lock()
|
||||||
defer room.lock.Unlock()
|
defer room.lock.Unlock()
|
||||||
room.changed = true
|
room.changed = true
|
||||||
_, exists := room.state[event.Type]
|
_, exists := room.state[evt.Type]
|
||||||
if !exists {
|
if !exists {
|
||||||
room.state[event.Type] = make(map[string]*mautrix.Event)
|
room.state[evt.Type] = make(map[string]*event.Event)
|
||||||
}
|
}
|
||||||
switch event.Type {
|
switch evt.Type {
|
||||||
case mautrix.StateRoomName:
|
case event.StateRoomName:
|
||||||
room.NameCache = event.Content.Name
|
room.NameCache = evt.Content.Name
|
||||||
room.nameCacheSource = ExplicitRoomName
|
room.nameCacheSource = ExplicitRoomName
|
||||||
case mautrix.StateCanonicalAlias:
|
case event.StateCanonicalAlias:
|
||||||
if room.nameCacheSource <= CanonicalAliasRoomName {
|
if room.nameCacheSource <= CanonicalAliasRoomName {
|
||||||
room.NameCache = event.Content.Alias
|
room.NameCache = string(evt.Content.Alias)
|
||||||
room.nameCacheSource = CanonicalAliasRoomName
|
room.nameCacheSource = CanonicalAliasRoomName
|
||||||
}
|
}
|
||||||
room.CanonicalAliasCache = event.Content.Alias
|
room.CanonicalAliasCache = evt.Content.Alias
|
||||||
case mautrix.StateMember:
|
case event.StateMember:
|
||||||
if room.nameCacheSource <= MemberRoomName {
|
if room.nameCacheSource <= MemberRoomName {
|
||||||
room.NameCache = ""
|
room.NameCache = ""
|
||||||
}
|
}
|
||||||
room.updateMemberState(event)
|
room.updateMemberState(evt)
|
||||||
case mautrix.StateTopic:
|
case event.StateTopic:
|
||||||
room.topicCache = event.Content.Topic
|
room.topicCache = evt.Content.Topic
|
||||||
}
|
}
|
||||||
|
|
||||||
if event.Type != mautrix.StateMember {
|
if evt.Type != event.StateMember {
|
||||||
debug.Printf("Updating state %s#%s for %s", event.Type.String(), event.GetStateKey(), room.ID)
|
debug.Printf("Updating state %s#%s for %s", evt.Type.String(), evt.GetStateKey(), room.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
room.state[event.Type][*event.StateKey] = event
|
room.state[evt.Type][*evt.StateKey] = evt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (room *Room) updateMemberState(event *mautrix.Event) {
|
func (room *Room) updateMemberState(event *event.Event) {
|
||||||
userID := event.GetStateKey()
|
userID := id.UserID(event.GetStateKey())
|
||||||
if userID == room.SessionUserID {
|
if userID == room.SessionUserID {
|
||||||
debug.Print("Updating session user state:", string(event.Content.VeryRaw))
|
debug.Print("Updating session user state:", string(event.Content.VeryRaw))
|
||||||
room.SessionMember = room.eventToMember(userID, event.Sender, &event.Content)
|
room.SessionMember = room.eventToMember(userID, event.Sender, &event.Content)
|
||||||
@ -442,7 +453,7 @@ func (room *Room) updateMemberState(event *mautrix.Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetStateEvent returns the state event for the given type/state_key combo, or nil.
|
// GetStateEvent returns the state event for the given type/state_key combo, or nil.
|
||||||
func (room *Room) GetStateEvent(eventType mautrix.EventType, stateKey string) *mautrix.Event {
|
func (room *Room) GetStateEvent(eventType event.Type, stateKey string) *event.Event {
|
||||||
room.Load()
|
room.Load()
|
||||||
room.lock.RLock()
|
room.lock.RLock()
|
||||||
defer room.lock.RUnlock()
|
defer room.lock.RUnlock()
|
||||||
@ -452,7 +463,7 @@ func (room *Room) GetStateEvent(eventType mautrix.EventType, stateKey string) *m
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getStateEvents returns the state events for the given type.
|
// getStateEvents returns the state events for the given type.
|
||||||
func (room *Room) getStateEvents(eventType mautrix.EventType) map[string]*mautrix.Event {
|
func (room *Room) getStateEvents(eventType event.Type) map[string]*event.Event {
|
||||||
stateEventMap, _ := room.state[eventType]
|
stateEventMap, _ := room.state[eventType]
|
||||||
return stateEventMap
|
return stateEventMap
|
||||||
}
|
}
|
||||||
@ -460,7 +471,7 @@ func (room *Room) getStateEvents(eventType mautrix.EventType) map[string]*mautri
|
|||||||
// GetTopic returns the topic of the room.
|
// GetTopic returns the topic of the room.
|
||||||
func (room *Room) GetTopic() string {
|
func (room *Room) GetTopic() string {
|
||||||
if len(room.topicCache) == 0 {
|
if len(room.topicCache) == 0 {
|
||||||
topicEvt := room.GetStateEvent(mautrix.StateTopic, "")
|
topicEvt := room.GetStateEvent(event.StateTopic, "")
|
||||||
if topicEvt != nil {
|
if topicEvt != nil {
|
||||||
room.topicCache = topicEvt.Content.Topic
|
room.topicCache = topicEvt.Content.Topic
|
||||||
}
|
}
|
||||||
@ -468,9 +479,9 @@ func (room *Room) GetTopic() string {
|
|||||||
return room.topicCache
|
return room.topicCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (room *Room) GetCanonicalAlias() string {
|
func (room *Room) GetCanonicalAlias() id.RoomAlias {
|
||||||
if len(room.CanonicalAliasCache) == 0 {
|
if len(room.CanonicalAliasCache) == 0 {
|
||||||
canonicalAliasEvt := room.GetStateEvent(mautrix.StateCanonicalAlias, "")
|
canonicalAliasEvt := room.GetStateEvent(event.StateCanonicalAlias, "")
|
||||||
if canonicalAliasEvt != nil {
|
if canonicalAliasEvt != nil {
|
||||||
room.CanonicalAliasCache = canonicalAliasEvt.Content.Alias
|
room.CanonicalAliasCache = canonicalAliasEvt.Content.Alias
|
||||||
} else {
|
} else {
|
||||||
@ -485,7 +496,7 @@ func (room *Room) GetCanonicalAlias() string {
|
|||||||
|
|
||||||
// updateNameFromNameEvent updates the room display name to be the name set in the name event.
|
// updateNameFromNameEvent updates the room display name to be the name set in the name event.
|
||||||
func (room *Room) updateNameFromNameEvent() {
|
func (room *Room) updateNameFromNameEvent() {
|
||||||
nameEvt := room.GetStateEvent(mautrix.StateRoomName, "")
|
nameEvt := room.GetStateEvent(event.StateRoomName, "")
|
||||||
if nameEvt != nil {
|
if nameEvt != nil {
|
||||||
room.NameCache = nameEvt.Content.Name
|
room.NameCache = nameEvt.Content.Name
|
||||||
}
|
}
|
||||||
@ -528,7 +539,7 @@ func (room *Room) updateNameCache() {
|
|||||||
room.nameCacheSource = ExplicitRoomName
|
room.nameCacheSource = ExplicitRoomName
|
||||||
}
|
}
|
||||||
if len(room.NameCache) == 0 {
|
if len(room.NameCache) == 0 {
|
||||||
room.NameCache = room.GetCanonicalAlias()
|
room.NameCache = string(room.GetCanonicalAlias())
|
||||||
room.nameCacheSource = CanonicalAliasRoomName
|
room.nameCacheSource = CanonicalAliasRoomName
|
||||||
}
|
}
|
||||||
if len(room.NameCache) == 0 {
|
if len(room.NameCache) == 0 {
|
||||||
@ -548,8 +559,8 @@ func (room *Room) GetTitle() string {
|
|||||||
|
|
||||||
func (room *Room) IsReplaced() bool {
|
func (room *Room) IsReplaced() bool {
|
||||||
if room.replacedByCache == nil {
|
if room.replacedByCache == nil {
|
||||||
evt := room.GetStateEvent(mautrix.StateTombstone, "")
|
evt := room.GetStateEvent(event.StateTombstone, "")
|
||||||
var replacement string
|
var replacement id.RoomID
|
||||||
if evt != nil {
|
if evt != nil {
|
||||||
replacement = evt.Content.ReplacementRoom
|
replacement = evt.Content.ReplacementRoom
|
||||||
}
|
}
|
||||||
@ -559,18 +570,18 @@ func (room *Room) IsReplaced() bool {
|
|||||||
return room.replacedCache
|
return room.replacedCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (room *Room) ReplacedBy() string {
|
func (room *Room) ReplacedBy() id.RoomID {
|
||||||
if room.replacedByCache == nil {
|
if room.replacedByCache == nil {
|
||||||
room.IsReplaced()
|
room.IsReplaced()
|
||||||
}
|
}
|
||||||
return *room.replacedByCache
|
return *room.replacedByCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (room *Room) eventToMember(userID string, sender string, content *mautrix.Content) *Member {
|
func (room *Room) eventToMember(userID, sender id.UserID, content *event.Content) *Member {
|
||||||
member := content.Member
|
member := content.Member
|
||||||
member.Membership = content.Membership
|
member.Membership = content.Membership
|
||||||
if len(member.Displayname) == 0 {
|
if len(member.Displayname) == 0 {
|
||||||
member.Displayname = userID
|
member.Displayname = string(userID)
|
||||||
}
|
}
|
||||||
return &Member{
|
return &Member{
|
||||||
Member: member,
|
Member: member,
|
||||||
@ -578,7 +589,7 @@ func (room *Room) eventToMember(userID string, sender string, content *mautrix.C
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (room *Room) updateNthMemberCache(userID string, member *Member) {
|
func (room *Room) updateNthMemberCache(userID id.UserID, member *Member) {
|
||||||
if userID != room.SessionUserID {
|
if userID != room.SessionUserID {
|
||||||
if room.firstMemberCache == nil {
|
if room.firstMemberCache == nil {
|
||||||
room.firstMemberCache = member
|
room.firstMemberCache = member
|
||||||
@ -589,19 +600,20 @@ func (room *Room) updateNthMemberCache(userID string, member *Member) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// createMemberCache caches all member events into a easily processable MXID -> *Member map.
|
// createMemberCache caches all member events into a easily processable MXID -> *Member map.
|
||||||
func (room *Room) createMemberCache() map[string]*Member {
|
func (room *Room) createMemberCache() map[id.UserID]*Member {
|
||||||
if len(room.memberCache) > 0 {
|
if len(room.memberCache) > 0 {
|
||||||
return room.memberCache
|
return room.memberCache
|
||||||
}
|
}
|
||||||
cache := make(map[string]*Member)
|
cache := make(map[id.UserID]*Member)
|
||||||
exCache := make(map[string]*Member)
|
exCache := make(map[id.UserID]*Member)
|
||||||
room.lock.RLock()
|
room.lock.RLock()
|
||||||
events := room.getStateEvents(mautrix.StateMember)
|
memberEvents := room.getStateEvents(event.StateMember)
|
||||||
room.firstMemberCache = nil
|
room.firstMemberCache = nil
|
||||||
room.secondMemberCache = nil
|
room.secondMemberCache = nil
|
||||||
if events != nil {
|
if memberEvents != nil {
|
||||||
for userID, event := range events {
|
for userIDStr, evt := range memberEvents {
|
||||||
member := room.eventToMember(userID, event.Sender, &event.Content)
|
userID := id.UserID(userIDStr)
|
||||||
|
member := room.eventToMember(userID, evt.Sender, &evt.Content)
|
||||||
if member.Membership.IsInviteOrJoin() {
|
if member.Membership.IsInviteOrJoin() {
|
||||||
cache[userID] = member
|
cache[userID] = member
|
||||||
room.updateNthMemberCache(userID, member)
|
room.updateNthMemberCache(userID, member)
|
||||||
@ -631,7 +643,7 @@ func (room *Room) createMemberCache() map[string]*Member {
|
|||||||
//
|
//
|
||||||
// The members are returned from the cache.
|
// The members are returned from the cache.
|
||||||
// If the cache is empty, it is updated first.
|
// If the cache is empty, it is updated first.
|
||||||
func (room *Room) GetMembers() map[string]*Member {
|
func (room *Room) GetMembers() map[id.UserID]*Member {
|
||||||
room.Load()
|
room.Load()
|
||||||
room.createMemberCache()
|
room.createMemberCache()
|
||||||
return room.memberCache
|
return room.memberCache
|
||||||
@ -639,7 +651,7 @@ func (room *Room) GetMembers() map[string]*Member {
|
|||||||
|
|
||||||
// GetMember returns the member with the given MXID.
|
// GetMember returns the member with the given MXID.
|
||||||
// If the member doesn't exist, nil is returned.
|
// If the member doesn't exist, nil is returned.
|
||||||
func (room *Room) GetMember(userID string) *Member {
|
func (room *Room) GetMember(userID id.UserID) *Member {
|
||||||
if userID == room.SessionUserID && room.SessionMember != nil {
|
if userID == room.SessionUserID && room.SessionMember != nil {
|
||||||
return room.SessionMember
|
return room.SessionMember
|
||||||
}
|
}
|
||||||
@ -660,16 +672,27 @@ func (room *Room) GetMember(userID string) *Member {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (room *Room) GetMemberCount() int {
|
||||||
|
if room.memberCache == nil && room.Summary.JoinedMemberCount != nil {
|
||||||
|
return *room.Summary.JoinedMemberCount
|
||||||
|
}
|
||||||
|
return len(room.GetMembers())
|
||||||
|
}
|
||||||
|
|
||||||
// GetSessionOwner returns the ID of the user whose session this room was created for.
|
// GetSessionOwner returns the ID of the user whose session this room was created for.
|
||||||
func (room *Room) GetSessionOwner() string {
|
func (room *Room) GetOwnDisplayname() string {
|
||||||
return room.SessionUserID
|
member := room.GetMember(room.SessionUserID)
|
||||||
|
if member != nil {
|
||||||
|
return member.Displayname
|
||||||
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRoom creates a new Room with the given ID
|
// NewRoom creates a new Room with the given ID
|
||||||
func NewRoom(roomID string, cache *RoomCache) *Room {
|
func NewRoom(roomID id.RoomID, cache *RoomCache) *Room {
|
||||||
return &Room{
|
return &Room{
|
||||||
ID: roomID,
|
ID: roomID,
|
||||||
state: make(map[mautrix.EventType]map[string]*mautrix.Event),
|
state: make(map[event.Type]map[string]*event.Event),
|
||||||
path: cache.roomPath(roomID),
|
path: cache.roomPath(roomID),
|
||||||
cache: cache,
|
cache: cache,
|
||||||
|
|
||||||
|
@ -1,237 +0,0 @@
|
|||||||
// gomuks - A terminal Matrix client written in Go.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package rooms_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
|
||||||
"maunium.net/go/mautrix"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewRoom_DefaultValues(t *testing.T) {
|
|
||||||
room := rooms.NewRoom("!test:maunium.net", "@tulir:maunium.net")
|
|
||||||
assert.Equal(t, "!test:maunium.net", room.ID)
|
|
||||||
assert.Equal(t, "@tulir:maunium.net", room.SessionUserID)
|
|
||||||
assert.Empty(t, room.GetMembers())
|
|
||||||
assert.Equal(t, "Empty room", room.GetTitle())
|
|
||||||
assert.Empty(t, room.GetAliases())
|
|
||||||
assert.Empty(t, room.GetCanonicalAlias())
|
|
||||||
assert.Empty(t, room.GetTopic())
|
|
||||||
assert.Nil(t, room.GetMember(room.GetSessionOwner()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRoom_GetCanonicalAlias(t *testing.T) {
|
|
||||||
room := rooms.NewRoom("!test:maunium.net", "@tulir:maunium.net")
|
|
||||||
room.UpdateState(&mautrix.Event{
|
|
||||||
Type: mautrix.StateCanonicalAlias,
|
|
||||||
Content: mautrix.Content{
|
|
||||||
Alias: "#foo:maunium.net",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
assert.Equal(t, "#foo:maunium.net", room.GetCanonicalAlias())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRoom_GetTopic(t *testing.T) {
|
|
||||||
room := rooms.NewRoom("!test:maunium.net", "@tulir:maunium.net")
|
|
||||||
room.UpdateState(&mautrix.Event{
|
|
||||||
Type: mautrix.StateTopic,
|
|
||||||
Content: mautrix.Content{
|
|
||||||
Topic: "test topic",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
assert.Equal(t, "test topic", room.GetTopic())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRoom_Tags_Empty(t *testing.T) {
|
|
||||||
room := rooms.NewRoom("!test:maunium.net", "@tulir:maunium.net")
|
|
||||||
assert.Empty(t, room.RawTags)
|
|
||||||
tags := room.Tags()
|
|
||||||
assert.Len(t, tags, 1)
|
|
||||||
assert.Equal(t, "", tags[0].Tag)
|
|
||||||
assert.Equal(t, "0.5", tags[0].Order)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRoom_Tags_NotEmpty(t *testing.T) {
|
|
||||||
room := rooms.NewRoom("!test:maunium.net", "@tulir:maunium.net")
|
|
||||||
room.RawTags = []rooms.RoomTag{{Tag: "foo", Order: "1"}, {Tag: "bar", Order: "1"}}
|
|
||||||
tags := room.Tags()
|
|
||||||
assert.Equal(t, room.RawTags, tags)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRoom_GetAliases(t *testing.T) {
|
|
||||||
room := rooms.NewRoom("!test:maunium.net", "@tulir:maunium.net")
|
|
||||||
addAliases(room)
|
|
||||||
|
|
||||||
aliases := room.GetAliases()
|
|
||||||
assert.Contains(t, aliases, "#bar:maunium.net")
|
|
||||||
assert.Contains(t, aliases, "#test:maunium.net")
|
|
||||||
assert.Contains(t, aliases, "#foo:matrix.org")
|
|
||||||
assert.Contains(t, aliases, "#test:matrix.org")
|
|
||||||
}
|
|
||||||
|
|
||||||
func addName(room *rooms.Room) {
|
|
||||||
room.UpdateState(&mautrix.Event{
|
|
||||||
Type: mautrix.StateRoomName,
|
|
||||||
Content: mautrix.Content{
|
|
||||||
Name: "Test room",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func addCanonicalAlias(room *rooms.Room) {
|
|
||||||
room.UpdateState(&mautrix.Event{
|
|
||||||
Type: mautrix.StateCanonicalAlias,
|
|
||||||
Content: mautrix.Content{
|
|
||||||
Alias: "#foo:maunium.net",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func addAliases(room *rooms.Room) {
|
|
||||||
server1 := "maunium.net"
|
|
||||||
room.UpdateState(&mautrix.Event{
|
|
||||||
Type: mautrix.StateAliases,
|
|
||||||
StateKey: &server1,
|
|
||||||
Content: mautrix.Content{
|
|
||||||
Aliases: []string{"#bar:maunium.net", "#test:maunium.net", "#foo:maunium.net"},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
server2 := "matrix.org"
|
|
||||||
room.UpdateState(&mautrix.Event{
|
|
||||||
Type: mautrix.StateAliases,
|
|
||||||
StateKey: &server2,
|
|
||||||
Content: mautrix.Content{
|
|
||||||
Aliases: []string{"#foo:matrix.org", "#test:matrix.org"},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func addMembers(room *rooms.Room, count int) {
|
|
||||||
user1 := "@tulir:maunium.net"
|
|
||||||
room.UpdateState(&mautrix.Event{
|
|
||||||
Type: mautrix.StateMember,
|
|
||||||
StateKey: &user1,
|
|
||||||
Content: mautrix.Content{
|
|
||||||
Member: mautrix.Member{
|
|
||||||
Displayname: "tulir",
|
|
||||||
Membership: mautrix.MembershipJoin,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
for i := 1; i < count; i++ {
|
|
||||||
userN := fmt.Sprintf("@user_%d:matrix.org", i+1)
|
|
||||||
content := mautrix.Content{
|
|
||||||
Member: mautrix.Member{
|
|
||||||
Membership: mautrix.MembershipJoin,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if i%2 == 1 {
|
|
||||||
content.Displayname = fmt.Sprintf("User #%d", i+1)
|
|
||||||
}
|
|
||||||
if i%5 == 0 {
|
|
||||||
content.Membership = mautrix.MembershipInvite
|
|
||||||
}
|
|
||||||
room.UpdateState(&mautrix.Event{
|
|
||||||
Type: mautrix.StateMember,
|
|
||||||
StateKey: &userN,
|
|
||||||
Content: content,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRoom_GetMembers(t *testing.T) {
|
|
||||||
room := rooms.NewRoom("!test:maunium.net", "@tulir:maunium.net")
|
|
||||||
addMembers(room, 6)
|
|
||||||
|
|
||||||
members := room.GetMembers()
|
|
||||||
assert.Len(t, members, 6)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRoom_GetMember(t *testing.T) {
|
|
||||||
room := rooms.NewRoom("!test:maunium.net", "@tulir:maunium.net")
|
|
||||||
addMembers(room, 6)
|
|
||||||
|
|
||||||
assert.NotNil(t, room.GetMember("@user_2:matrix.org"))
|
|
||||||
assert.NotNil(t, room.GetMember("@tulir:maunium.net"))
|
|
||||||
assert.Equal(t, "@tulir:maunium.net", room.GetSessionOwner())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRoom_GetTitle_ExplicitName(t *testing.T) {
|
|
||||||
room := rooms.NewRoom("!test:maunium.net", "@tulir:maunium.net")
|
|
||||||
addMembers(room, 4)
|
|
||||||
addName(room)
|
|
||||||
addCanonicalAlias(room)
|
|
||||||
addAliases(room)
|
|
||||||
assert.Equal(t, "Test room", room.GetTitle())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRoom_GetTitle_CanonicalAlias(t *testing.T) {
|
|
||||||
room := rooms.NewRoom("!test:maunium.net", "@tulir:maunium.net")
|
|
||||||
addMembers(room, 4)
|
|
||||||
addCanonicalAlias(room)
|
|
||||||
addAliases(room)
|
|
||||||
assert.Equal(t, "#foo:maunium.net", room.GetTitle())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRoom_GetTitle_FirstAlias(t *testing.T) {
|
|
||||||
room := rooms.NewRoom("!test:maunium.net", "@tulir:maunium.net")
|
|
||||||
addMembers(room, 2)
|
|
||||||
addAliases(room)
|
|
||||||
assert.Equal(t, "#bar:maunium.net", room.GetTitle())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRoom_GetTitle_Members_Empty(t *testing.T) {
|
|
||||||
room := rooms.NewRoom("!test:maunium.net", "@tulir:maunium.net")
|
|
||||||
addMembers(room, 1)
|
|
||||||
assert.Equal(t, "Empty room", room.GetTitle())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRoom_GetTitle_Members_OneToOne(t *testing.T) {
|
|
||||||
room := rooms.NewRoom("!test:maunium.net", "@tulir:maunium.net")
|
|
||||||
addMembers(room, 2)
|
|
||||||
assert.Equal(t, "User #2", room.GetTitle())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRoom_GetTitle_Members_GroupChat(t *testing.T) {
|
|
||||||
room := rooms.NewRoom("!test:maunium.net", "@tulir:maunium.net")
|
|
||||||
addMembers(room, 76)
|
|
||||||
assert.Contains(t, room.GetTitle(), " and 74 others")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRoom_MarkRead(t *testing.T) {
|
|
||||||
room := rooms.NewRoom("!test:maunium.net", "@tulir:maunium.net")
|
|
||||||
|
|
||||||
room.AddUnread("foo", true, false)
|
|
||||||
assert.Equal(t, 1, room.UnreadCount())
|
|
||||||
assert.False(t, room.Highlighted())
|
|
||||||
|
|
||||||
room.AddUnread("bar", true, false)
|
|
||||||
assert.Equal(t, 2, room.UnreadCount())
|
|
||||||
assert.False(t, room.Highlighted())
|
|
||||||
|
|
||||||
room.AddUnread("asd", false, true)
|
|
||||||
assert.Equal(t, 2, room.UnreadCount())
|
|
||||||
assert.True(t, room.Highlighted())
|
|
||||||
|
|
||||||
room.MarkRead("asd")
|
|
||||||
assert.Empty(t, room.UnreadMessages)
|
|
||||||
}
|
|
@ -27,6 +27,7 @@ import (
|
|||||||
sync "github.com/sasha-s/go-deadlock"
|
sync "github.com/sasha-s/go-deadlock"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/debug"
|
"maunium.net/go/gomuks/debug"
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RoomCache contains room state info in a hashmap and linked list.
|
// RoomCache contains room state info in a hashmap and linked list.
|
||||||
@ -37,15 +38,15 @@ type RoomCache struct {
|
|||||||
directory string
|
directory string
|
||||||
maxSize int
|
maxSize int
|
||||||
maxAge int64
|
maxAge int64
|
||||||
getOwner func() string
|
getOwner func() id.UserID
|
||||||
|
|
||||||
Map map[string]*Room
|
Map map[id.RoomID]*Room
|
||||||
head *Room
|
head *Room
|
||||||
tail *Room
|
tail *Room
|
||||||
size int
|
size int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRoomCache(listPath, directory string, maxSize int, maxAge int64, getOwner func() string) *RoomCache {
|
func NewRoomCache(listPath, directory string, maxSize int, maxAge int64, getOwner func() id.UserID) *RoomCache {
|
||||||
return &RoomCache{
|
return &RoomCache{
|
||||||
listPath: listPath,
|
listPath: listPath,
|
||||||
directory: directory,
|
directory: directory,
|
||||||
@ -53,7 +54,7 @@ func NewRoomCache(listPath, directory string, maxSize int, maxAge int64, getOwne
|
|||||||
maxAge: maxAge,
|
maxAge: maxAge,
|
||||||
getOwner: getOwner,
|
getOwner: getOwner,
|
||||||
|
|
||||||
Map: make(map[string]*Room),
|
Map: make(map[id.RoomID]*Room),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +89,7 @@ func (cache *RoomCache) LoadList() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read list
|
// Read list
|
||||||
cache.Map = make(map[string]*Room, size)
|
cache.Map = make(map[id.RoomID]*Room, size)
|
||||||
for i := 0; i < size; i++ {
|
for i := 0; i < size; i++ {
|
||||||
room := &Room{}
|
room := &Room{}
|
||||||
err = dec.Decode(room)
|
err = dec.Decode(room)
|
||||||
@ -147,7 +148,7 @@ func (cache *RoomCache) SaveList() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cache *RoomCache) Touch(roomID string) {
|
func (cache *RoomCache) Touch(roomID id.RoomID) {
|
||||||
cache.Lock()
|
cache.Lock()
|
||||||
node, ok := cache.Map[roomID]
|
node, ok := cache.Map[roomID]
|
||||||
if !ok || node == nil {
|
if !ok || node == nil {
|
||||||
@ -174,14 +175,14 @@ func (cache *RoomCache) touch(node *Room) {
|
|||||||
node.touch = time.Now().Unix()
|
node.touch = time.Now().Unix()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cache *RoomCache) Get(roomID string) *Room {
|
func (cache *RoomCache) Get(roomID id.RoomID) *Room {
|
||||||
cache.Lock()
|
cache.Lock()
|
||||||
node := cache.get(roomID)
|
node := cache.get(roomID)
|
||||||
cache.Unlock()
|
cache.Unlock()
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cache *RoomCache) GetOrCreate(roomID string) *Room {
|
func (cache *RoomCache) GetOrCreate(roomID id.RoomID) *Room {
|
||||||
cache.Lock()
|
cache.Lock()
|
||||||
node := cache.get(roomID)
|
node := cache.get(roomID)
|
||||||
if node == nil {
|
if node == nil {
|
||||||
@ -192,7 +193,7 @@ func (cache *RoomCache) GetOrCreate(roomID string) *Room {
|
|||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cache *RoomCache) get(roomID string) *Room {
|
func (cache *RoomCache) get(roomID id.RoomID) *Room {
|
||||||
node, ok := cache.Map[roomID]
|
node, ok := cache.Map[roomID]
|
||||||
if ok && node != nil {
|
if ok && node != nil {
|
||||||
return node
|
return node
|
||||||
@ -215,11 +216,11 @@ func (cache *RoomCache) Put(room *Room) {
|
|||||||
node.Save()
|
node.Save()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cache *RoomCache) roomPath(roomID string) string {
|
func (cache *RoomCache) roomPath(roomID id.RoomID) string {
|
||||||
return filepath.Join(cache.directory, roomID+".gob.gz")
|
return filepath.Join(cache.directory, string(roomID)+".gob.gz")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cache *RoomCache) Load(roomID string) *Room {
|
func (cache *RoomCache) Load(roomID id.RoomID) *Room {
|
||||||
cache.Lock()
|
cache.Lock()
|
||||||
defer cache.Unlock()
|
defer cache.Unlock()
|
||||||
node, ok := cache.Map[roomID]
|
node, ok := cache.Map[roomID]
|
||||||
@ -312,7 +313,7 @@ func (cache *RoomCache) Unload(node *Room) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cache *RoomCache) newRoom(roomID string) *Room {
|
func (cache *RoomCache) newRoom(roomID id.RoomID) *Room {
|
||||||
node := NewRoom(roomID, cache)
|
node := NewRoom(roomID, cache)
|
||||||
cache.Map[node.ID] = node
|
cache.Map[node.ID] = node
|
||||||
return node
|
return node
|
||||||
|
114
matrix/sync.go
114
matrix/sync.go
@ -24,14 +24,16 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
|
"maunium.net/go/mautrix/event"
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/debug"
|
"maunium.net/go/gomuks/debug"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SyncerSession interface {
|
type SyncerSession interface {
|
||||||
GetRoom(id string) *rooms.Room
|
GetRoom(id id.RoomID) *rooms.Room
|
||||||
GetUserID() string
|
GetUserID() id.UserID
|
||||||
}
|
}
|
||||||
|
|
||||||
type EventSource int
|
type EventSource int
|
||||||
@ -45,6 +47,7 @@ const (
|
|||||||
EventSourceTimeline
|
EventSourceTimeline
|
||||||
EventSourceState
|
EventSourceState
|
||||||
EventSourceEphemeral
|
EventSourceEphemeral
|
||||||
|
EventSourceToDevice
|
||||||
)
|
)
|
||||||
|
|
||||||
func (es EventSource) String() string {
|
func (es EventSource) String() string {
|
||||||
@ -83,14 +86,14 @@ func (es EventSource) String() string {
|
|||||||
return fmt.Sprintf("unknown (%d)", es)
|
return fmt.Sprintf("unknown (%d)", es)
|
||||||
}
|
}
|
||||||
|
|
||||||
type EventHandler func(source EventSource, event *mautrix.Event)
|
type EventHandler func(source EventSource, event *event.Event)
|
||||||
|
|
||||||
// GomuksSyncer is the default syncing implementation. You can either write your own syncer, or selectively
|
// GomuksSyncer is the default syncing implementation. You can either write your own syncer, or selectively
|
||||||
// replace parts of this default syncer (e.g. the ProcessResponse method). The default syncer uses the observer
|
// replace parts of this default syncer (e.g. the ProcessResponse method). The default syncer uses the observer
|
||||||
// pattern to notify callers about incoming events. See GomuksSyncer.OnEventType for more information.
|
// pattern to notify callers about incoming events. See GomuksSyncer.OnEventType for more information.
|
||||||
type GomuksSyncer struct {
|
type GomuksSyncer struct {
|
||||||
Session SyncerSession
|
Session SyncerSession
|
||||||
listeners map[mautrix.EventType][]EventHandler // event type to listeners array
|
listeners map[event.Type][]EventHandler // event type to listeners array
|
||||||
FirstSyncDone bool
|
FirstSyncDone bool
|
||||||
InitDoneCallback func()
|
InitDoneCallback func()
|
||||||
}
|
}
|
||||||
@ -99,7 +102,7 @@ type GomuksSyncer struct {
|
|||||||
func NewGomuksSyncer(session SyncerSession) *GomuksSyncer {
|
func NewGomuksSyncer(session SyncerSession) *GomuksSyncer {
|
||||||
return &GomuksSyncer{
|
return &GomuksSyncer{
|
||||||
Session: session,
|
Session: session,
|
||||||
listeners: make(map[mautrix.EventType][]EventHandler),
|
listeners: make(map[event.Type][]EventHandler),
|
||||||
FirstSyncDone: false,
|
FirstSyncDone: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -152,33 +155,44 @@ func (s *GomuksSyncer) ProcessResponse(res *mautrix.RespSync, since string) (err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *GomuksSyncer) processSyncEvents(room *rooms.Room, events []json.RawMessage, source EventSource) {
|
func (s *GomuksSyncer) processSyncEvents(room *rooms.Room, events []json.RawMessage, source EventSource) {
|
||||||
for _, event := range events {
|
for _, evt := range events {
|
||||||
if source == EventSourcePresence {
|
s.processSyncEvent(room, evt, source)
|
||||||
debug.Print(string(event))
|
|
||||||
}
|
|
||||||
s.processSyncEvent(room, event, source)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GomuksSyncer) processSyncEvent(room *rooms.Room, eventJSON json.RawMessage, source EventSource) {
|
func (s *GomuksSyncer) processSyncEvent(room *rooms.Room, eventJSON json.RawMessage, source EventSource) {
|
||||||
event := &mautrix.Event{}
|
evt := &event.Event{}
|
||||||
err := json.Unmarshal(eventJSON, event)
|
err := json.Unmarshal(eventJSON, evt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Print("Failed to unmarshal event: %v\n%s", err, string(eventJSON))
|
debug.Print("Failed to unmarshal event: %v\n%s", err, string(eventJSON))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Ensure the type class is correct. It's safe to mutate since it's not a pointer.
|
||||||
|
// Listeners are keyed by type structs, which means only the correct class will pass.
|
||||||
|
switch {
|
||||||
|
case evt.StateKey != nil:
|
||||||
|
evt.Type.Class = event.StateEventType
|
||||||
|
case source == EventSourcePresence, source & EventSourceEphemeral != 0:
|
||||||
|
evt.Type.Class = event.EphemeralEventType
|
||||||
|
case source & EventSourceAccountData != 0:
|
||||||
|
evt.Type.Class = event.AccountDataEventType
|
||||||
|
case source == EventSourceToDevice:
|
||||||
|
evt.Type.Class = event.ToDeviceEventType
|
||||||
|
default:
|
||||||
|
evt.Type.Class = event.MessageEventType
|
||||||
|
}
|
||||||
if room != nil {
|
if room != nil {
|
||||||
event.RoomID = room.ID
|
evt.RoomID = room.ID
|
||||||
if source&EventSourceState != 0 || (source&EventSourceTimeline != 0 && event.Type.IsState() && event.StateKey != nil) {
|
if evt.Type.IsState() {
|
||||||
room.UpdateState(event)
|
room.UpdateState(evt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.notifyListeners(source, event)
|
s.notifyListeners(source, evt)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnEventType allows callers to be notified when there are new events for the given event type.
|
// OnEventType allows callers to be notified when there are new events for the given event type.
|
||||||
// There are no duplicate checks.
|
// There are no duplicate checks.
|
||||||
func (s *GomuksSyncer) OnEventType(eventType mautrix.EventType, callback EventHandler) {
|
func (s *GomuksSyncer) OnEventType(eventType event.Type, callback EventHandler) {
|
||||||
_, exists := s.listeners[eventType]
|
_, exists := s.listeners[eventType]
|
||||||
if !exists {
|
if !exists {
|
||||||
s.listeners[eventType] = []EventHandler{}
|
s.listeners[eventType] = []EventHandler{}
|
||||||
@ -186,21 +200,13 @@ func (s *GomuksSyncer) OnEventType(eventType mautrix.EventType, callback EventHa
|
|||||||
s.listeners[eventType] = append(s.listeners[eventType], callback)
|
s.listeners[eventType] = append(s.listeners[eventType], callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GomuksSyncer) notifyListeners(source EventSource, event *mautrix.Event) {
|
func (s *GomuksSyncer) notifyListeners(source EventSource, evt *event.Event) {
|
||||||
if (event.Type.IsState() && source&EventSourceState == 0 && event.StateKey == nil) ||
|
listeners, exists := s.listeners[evt.Type]
|
||||||
(event.Type.IsAccountData() && source&EventSourceAccountData == 0) ||
|
|
||||||
(event.Type.IsEphemeral() && event.Type != mautrix.EphemeralEventPresence && source&EventSourceEphemeral == 0) ||
|
|
||||||
(event.Type == mautrix.EphemeralEventPresence && source&EventSourcePresence == 0) {
|
|
||||||
evtJson, _ := json.Marshal(event)
|
|
||||||
debug.Printf("Event of type %s received from mismatching source %s: %s", event.Type.String(), source.String(), string(evtJson))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
listeners, exists := s.listeners[event.Type]
|
|
||||||
if !exists {
|
if !exists {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, fn := range listeners {
|
for _, fn := range listeners {
|
||||||
fn(source, event)
|
fn(source, evt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,53 +217,51 @@ func (s *GomuksSyncer) OnFailedSync(res *mautrix.RespSync, err error) (time.Dura
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetFilterJSON returns a filter with a timeline limit of 50.
|
// GetFilterJSON returns a filter with a timeline limit of 50.
|
||||||
func (s *GomuksSyncer) GetFilterJSON(userID string) json.RawMessage {
|
func (s *GomuksSyncer) GetFilterJSON(_ id.UserID) json.RawMessage {
|
||||||
filter := &mautrix.Filter{
|
filter := &mautrix.Filter{
|
||||||
Room: mautrix.RoomFilter{
|
Room: mautrix.RoomFilter{
|
||||||
IncludeLeave: false,
|
IncludeLeave: false,
|
||||||
State: mautrix.FilterPart{
|
State: mautrix.FilterPart{
|
||||||
LazyLoadMembers: true,
|
LazyLoadMembers: true,
|
||||||
Types: []string{
|
Types: []event.Type{
|
||||||
"m.room.member",
|
event.StateMember,
|
||||||
"m.room.name",
|
event.StateRoomName,
|
||||||
"m.room.topic",
|
event.StateTopic,
|
||||||
"m.room.canonical_alias",
|
event.StateCanonicalAlias,
|
||||||
"m.room.aliases",
|
event.StatePowerLevels,
|
||||||
"m.room.power_levels",
|
event.StateTombstone,
|
||||||
"m.room.tombstone",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Timeline: mautrix.FilterPart{
|
Timeline: mautrix.FilterPart{
|
||||||
LazyLoadMembers: true,
|
LazyLoadMembers: true,
|
||||||
Types: []string{
|
Types: []event.Type{
|
||||||
"m.room.message",
|
event.EventMessage,
|
||||||
"m.room.redaction",
|
event.EventRedaction,
|
||||||
"m.room.encrypted",
|
event.EventEncrypted,
|
||||||
"m.sticker",
|
event.EventSticker,
|
||||||
"m.reaction",
|
event.EventReaction,
|
||||||
|
|
||||||
"m.room.member",
|
event.StateMember,
|
||||||
"m.room.name",
|
event.StateRoomName,
|
||||||
"m.room.topic",
|
event.StateTopic,
|
||||||
"m.room.canonical_alias",
|
event.StateCanonicalAlias,
|
||||||
"m.room.aliases",
|
event.StatePowerLevels,
|
||||||
"m.room.power_levels",
|
event.StateTombstone,
|
||||||
"m.room.tombstone",
|
|
||||||
},
|
},
|
||||||
// Limit: 50,
|
Limit: 50,
|
||||||
},
|
},
|
||||||
Ephemeral: mautrix.FilterPart{
|
Ephemeral: mautrix.FilterPart{
|
||||||
Types: []string{"m.typing", "m.receipt"},
|
Types: []event.Type{event.EphemeralEventTyping, event.EphemeralEventReceipt},
|
||||||
},
|
},
|
||||||
AccountData: mautrix.FilterPart{
|
AccountData: mautrix.FilterPart{
|
||||||
Types: []string{"m.tag"},
|
Types: []event.Type{event.AccountDataRoomTags},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
AccountData: mautrix.FilterPart{
|
AccountData: mautrix.FilterPart{
|
||||||
Types: []string{"m.push_rules", "m.direct", "net.maunium.gomuks.preferences"},
|
Types: []event.Type{event.AccountDataPushRules, event.AccountDataDirectChats, AccountDataGomuksPreferences},
|
||||||
},
|
},
|
||||||
Presence: mautrix.FilterPart{
|
Presence: mautrix.FilterPart{
|
||||||
NotTypes: []string{"*"},
|
NotTypes: []event.Type{event.NewEventType("*")},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
rawFilter, _ := json.Marshal(&filter)
|
rawFilter, _ := json.Marshal(&filter)
|
||||||
|
@ -1,219 +0,0 @@
|
|||||||
// gomuks - A terminal Matrix client written in Go.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package matrix_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"maunium.net/go/gomuks/matrix"
|
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
|
||||||
"maunium.net/go/mautrix"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGomuksSyncer_ProcessResponse_Initial(t *testing.T) {
|
|
||||||
syncer := matrix.NewGomuksSyncer(&mockSyncerSession{})
|
|
||||||
var initDoneCalled = false
|
|
||||||
syncer.InitDoneCallback = func() {
|
|
||||||
initDoneCalled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
syncer.ProcessResponse(newRespSync(), "")
|
|
||||||
assert.True(t, syncer.FirstSyncDone)
|
|
||||||
assert.True(t, initDoneCalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGomuksSyncer_ProcessResponse(t *testing.T) {
|
|
||||||
mss := &mockSyncerSession{
|
|
||||||
userID: "@tulir:maunium.net",
|
|
||||||
rooms: map[string]*rooms.Room{
|
|
||||||
"!foo:maunium.net": {
|
|
||||||
Room: mautrix.NewRoom("!foo:maunium.net"),
|
|
||||||
},
|
|
||||||
"!bar:maunium.net": {
|
|
||||||
Room: mautrix.NewRoom("!bar:maunium.net"),
|
|
||||||
},
|
|
||||||
"!test:maunium.net": {
|
|
||||||
Room: mautrix.NewRoom("!test:maunium.net"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ml := &mockListener{}
|
|
||||||
syncer := matrix.NewGomuksSyncer(mss)
|
|
||||||
syncer.OnEventType(mautrix.EventMessage, ml.receive)
|
|
||||||
syncer.OnEventType(mautrix.StateMember, ml.receive)
|
|
||||||
syncer.GetFilterJSON("@tulir:maunium.net")
|
|
||||||
|
|
||||||
joinEvt := &mautrix.Event{
|
|
||||||
ID: "!join:maunium.net",
|
|
||||||
Type: mautrix.StateMember,
|
|
||||||
Sender: "@tulir:maunium.net",
|
|
||||||
StateKey: ptr("̣@tulir:maunium.net"),
|
|
||||||
Content: mautrix.Content{
|
|
||||||
Membership: mautrix.MembershipJoin,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
messageEvt := &mautrix.Event{
|
|
||||||
ID: "!msg:maunium.net",
|
|
||||||
Type: mautrix.EventMessage,
|
|
||||||
Content: mautrix.Content{
|
|
||||||
Body: "foo",
|
|
||||||
MsgType: mautrix.MsgText,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
unhandledEvt := &mautrix.Event{
|
|
||||||
ID: "!unhandled:maunium.net",
|
|
||||||
Type: mautrix.EventType{Type: "m.room.unhandled_event"},
|
|
||||||
}
|
|
||||||
inviteEvt := &mautrix.Event{
|
|
||||||
ID: "!invite:matrix.org",
|
|
||||||
Type: mautrix.StateMember,
|
|
||||||
Sender: "@you:matrix.org",
|
|
||||||
StateKey: ptr("̣@tulir:maunium.net"),
|
|
||||||
Content: mautrix.Content{
|
|
||||||
Membership: mautrix.MembershipInvite,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
leaveEvt := &mautrix.Event{
|
|
||||||
ID: "!leave:matrix.org",
|
|
||||||
Type: mautrix.StateMember,
|
|
||||||
Sender: "@you:matrix.org",
|
|
||||||
StateKey: ptr("̣@tulir:maunium.net"),
|
|
||||||
Content: mautrix.Content{
|
|
||||||
Membership: mautrix.MembershipLeave,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := newRespSync()
|
|
||||||
resp.Rooms.Join["!foo:maunium.net"] = join{
|
|
||||||
State: events{Events: []*mautrix.Event{joinEvt}},
|
|
||||||
Timeline: timeline{Events: []*mautrix.Event{messageEvt, unhandledEvt}},
|
|
||||||
}
|
|
||||||
resp.Rooms.Invite["!bar:maunium.net"] = struct {
|
|
||||||
State struct {
|
|
||||||
Events []*mautrix.Event `json:"events"`
|
|
||||||
} `json:"invite_state"`
|
|
||||||
}{
|
|
||||||
State: events{Events: []*mautrix.Event{inviteEvt}},
|
|
||||||
}
|
|
||||||
resp.Rooms.Leave["!test:maunium.net"] = struct {
|
|
||||||
State struct {
|
|
||||||
Events []*mautrix.Event `json:"events"`
|
|
||||||
} `json:"state"`
|
|
||||||
Timeline struct {
|
|
||||||
Events []*mautrix.Event `json:"events"`
|
|
||||||
Limited bool `json:"limited"`
|
|
||||||
PrevBatch string `json:"prev_batch"`
|
|
||||||
} `json:"timeline"`
|
|
||||||
}{
|
|
||||||
State: events{Events: []*mautrix.Event{leaveEvt}},
|
|
||||||
}
|
|
||||||
|
|
||||||
syncer.ProcessResponse(resp, "since")
|
|
||||||
assert.Contains(t, ml.received, joinEvt, joinEvt.ID)
|
|
||||||
assert.Contains(t, ml.received, messageEvt, messageEvt.ID)
|
|
||||||
assert.NotContains(t, ml.received, unhandledEvt, unhandledEvt.ID)
|
|
||||||
assert.Contains(t, ml.received, inviteEvt, inviteEvt.ID)
|
|
||||||
assert.Contains(t, ml.received, leaveEvt, leaveEvt.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockSyncerSession struct {
|
|
||||||
rooms map[string]*rooms.Room
|
|
||||||
userID string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mss *mockSyncerSession) GetRoom(id string) *rooms.Room {
|
|
||||||
return mss.rooms[id]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mss *mockSyncerSession) GetUserID() string {
|
|
||||||
return mss.userID
|
|
||||||
}
|
|
||||||
|
|
||||||
type events struct {
|
|
||||||
Events []*mautrix.Event `json:"events"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type timeline struct {
|
|
||||||
Events []*mautrix.Event `json:"events"`
|
|
||||||
Limited bool `json:"limited"`
|
|
||||||
PrevBatch string `json:"prev_batch"`
|
|
||||||
}
|
|
||||||
type join struct {
|
|
||||||
State struct {
|
|
||||||
Events []*mautrix.Event `json:"events"`
|
|
||||||
} `json:"state"`
|
|
||||||
Timeline struct {
|
|
||||||
Events []*mautrix.Event `json:"events"`
|
|
||||||
Limited bool `json:"limited"`
|
|
||||||
PrevBatch string `json:"prev_batch"`
|
|
||||||
} `json:"timeline"`
|
|
||||||
Ephemeral struct {
|
|
||||||
Events []*mautrix.Event `json:"events"`
|
|
||||||
} `json:"ephemeral"`
|
|
||||||
AccountData struct {
|
|
||||||
Events []*mautrix.Event `json:"events"`
|
|
||||||
} `json:"account_data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func ptr(text string) *string {
|
|
||||||
return &text
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockListener struct {
|
|
||||||
received []*mautrix.Event
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ml *mockListener) receive(source matrix.EventSource, evt *mautrix.Event) {
|
|
||||||
ml.received = append(ml.received, evt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRespSync() *mautrix.RespSync {
|
|
||||||
resp := &mautrix.RespSync{NextBatch: "123"}
|
|
||||||
resp.Rooms.Join = make(map[string]struct {
|
|
||||||
State struct {
|
|
||||||
Events []*mautrix.Event `json:"events"`
|
|
||||||
} `json:"state"`
|
|
||||||
Timeline struct {
|
|
||||||
Events []*mautrix.Event `json:"events"`
|
|
||||||
Limited bool `json:"limited"`
|
|
||||||
PrevBatch string `json:"prev_batch"`
|
|
||||||
} `json:"timeline"`
|
|
||||||
Ephemeral struct {
|
|
||||||
Events []*mautrix.Event `json:"events"`
|
|
||||||
} `json:"ephemeral"`
|
|
||||||
AccountData struct {
|
|
||||||
Events []*mautrix.Event `json:"events"`
|
|
||||||
} `json:"account_data"`
|
|
||||||
})
|
|
||||||
resp.Rooms.Invite = make(map[string]struct {
|
|
||||||
State struct {
|
|
||||||
Events []*mautrix.Event `json:"events"`
|
|
||||||
} `json:"invite_state"`
|
|
||||||
})
|
|
||||||
resp.Rooms.Leave = make(map[string]struct {
|
|
||||||
State struct {
|
|
||||||
Events []*mautrix.Event `json:"events"`
|
|
||||||
} `json:"state"`
|
|
||||||
Timeline struct {
|
|
||||||
Events []*mautrix.Event `json:"events"`
|
|
||||||
Limited bool `json:"limited"`
|
|
||||||
PrevBatch string `json:"prev_batch"`
|
|
||||||
} `json:"timeline"`
|
|
||||||
})
|
|
||||||
return resp
|
|
||||||
}
|
|
@ -35,14 +35,16 @@ import (
|
|||||||
"github.com/russross/blackfriday/v2"
|
"github.com/russross/blackfriday/v2"
|
||||||
|
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
|
"maunium.net/go/mautrix/event"
|
||||||
"maunium.net/go/mautrix/format"
|
"maunium.net/go/mautrix/format"
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/debug"
|
"maunium.net/go/gomuks/debug"
|
||||||
)
|
)
|
||||||
|
|
||||||
func cmdMe(cmd *Command) {
|
func cmdMe(cmd *Command) {
|
||||||
text := strings.Join(cmd.Args, " ")
|
text := strings.Join(cmd.Args, " ")
|
||||||
go cmd.Room.SendMessage(mautrix.MsgEmote, text)
|
go cmd.Room.SendMessage(event.MsgEmote, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GradientTable from https://github.com/lucasb-eyer/go-colorful/blob/master/doc/gradientgen/gradientgen.go
|
// GradientTable from https://github.com/lucasb-eyer/go-colorful/blob/master/doc/gradientgen/gradientgen.go
|
||||||
@ -79,7 +81,7 @@ var rainbow = GradientTable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO this command definitely belongs in a plugin once we have a plugin system.
|
// TODO this command definitely belongs in a plugin once we have a plugin system.
|
||||||
func makeRainbow(cmd *Command, msgtype mautrix.MessageType) {
|
func makeRainbow(cmd *Command, msgtype event.MessageType) {
|
||||||
text := strings.Join(cmd.Args, " ")
|
text := strings.Join(cmd.Args, " ")
|
||||||
|
|
||||||
render := NewRainbowRenderer(blackfriday.NewHTMLRenderer(blackfriday.HTMLRendererParameters{
|
render := NewRainbowRenderer(blackfriday.NewHTMLRenderer(blackfriday.HTMLRendererParameters{
|
||||||
@ -101,15 +103,15 @@ func makeRainbow(cmd *Command, msgtype mautrix.MessageType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func cmdRainbow(cmd *Command) {
|
func cmdRainbow(cmd *Command) {
|
||||||
makeRainbow(cmd, mautrix.MsgText)
|
makeRainbow(cmd, event.MsgText)
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdRainbowMe(cmd *Command) {
|
func cmdRainbowMe(cmd *Command) {
|
||||||
makeRainbow(cmd, mautrix.MsgEmote)
|
makeRainbow(cmd, event.MsgEmote)
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdNotice(cmd *Command) {
|
func cmdNotice(cmd *Command) {
|
||||||
go cmd.Room.SendMessage(mautrix.MsgNotice, strings.Join(cmd.Args, " "))
|
go cmd.Room.SendMessage(event.MsgNotice, strings.Join(cmd.Args, " "))
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdAccept(cmd *Command) {
|
func cmdAccept(cmd *Command) {
|
||||||
@ -118,7 +120,7 @@ func cmdAccept(cmd *Command) {
|
|||||||
cmd.Reply("/accept can only be used in rooms you're invited to")
|
cmd.Reply("/accept can only be used in rooms you're invited to")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, server, _ := mautrix.ParseUserID(room.SessionMember.Sender)
|
_, server, _ := room.SessionMember.Sender.Parse()
|
||||||
_, err := cmd.Matrix.JoinRoom(room.ID, server)
|
_, err := cmd.Matrix.JoinRoom(room.ID, server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmd.Reply("Failed to accept invite:", err)
|
cmd.Reply("Failed to accept invite:", err)
|
||||||
@ -223,11 +225,11 @@ func cmdTag(cmd *Command) {
|
|||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
if len(cmd.Args) > 2 && cmd.Args[2] == "--reset" {
|
if len(cmd.Args) > 2 && cmd.Args[2] == "--reset" {
|
||||||
tags := mautrix.Tags{
|
tags := event.Tags{
|
||||||
cmd.Args[0]: {Order: json.Number(fmt.Sprintf("%f", order))},
|
cmd.Args[0]: {Order: json.Number(fmt.Sprintf("%f", order))},
|
||||||
}
|
}
|
||||||
for _, tag := range cmd.Room.MxRoom().RawTags {
|
for _, tag := range cmd.Room.MxRoom().RawTags {
|
||||||
tags[tag.Tag] = mautrix.Tag{Order: tag.Order}
|
tags[tag.Tag] = event.Tag{Order: tag.Order}
|
||||||
}
|
}
|
||||||
err = cmd.Matrix.Client().SetTags(cmd.Room.MxRoom().ID, tags)
|
err = cmd.Matrix.Client().SetTags(cmd.Room.MxRoom().ID, tags)
|
||||||
} else {
|
} else {
|
||||||
@ -253,7 +255,7 @@ func cmdRoomNick(cmd *Command) {
|
|||||||
room := cmd.Room.MxRoom()
|
room := cmd.Room.MxRoom()
|
||||||
member := room.GetMember(room.SessionUserID)
|
member := room.GetMember(room.SessionUserID)
|
||||||
member.Displayname = strings.Join(cmd.Args, " ")
|
member.Displayname = strings.Join(cmd.Args, " ")
|
||||||
_, err := cmd.Matrix.Client().SendStateEvent(room.ID, mautrix.StateMember, room.SessionUserID, member)
|
_, err := cmd.Matrix.Client().SendStateEvent(room.ID, event.StateMember, string(room.SessionUserID), member)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmd.Reply("Failed to set room nick:", err)
|
cmd.Reply("Failed to set room nick:", err)
|
||||||
}
|
}
|
||||||
@ -376,7 +378,7 @@ func cmdInvite(cmd *Command) {
|
|||||||
cmd.Reply("Usage: /invite <user id>")
|
cmd.Reply("Usage: /invite <user id>")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, err := cmd.Matrix.Client().InviteUser(cmd.Room.MxRoom().ID, &mautrix.ReqInviteUser{UserID: cmd.Args[0]})
|
_, err := cmd.Matrix.Client().InviteUser(cmd.Room.MxRoom().ID, &mautrix.ReqInviteUser{UserID: id.UserID(cmd.Args[0])})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Print("Error in invite call:", err)
|
debug.Print("Error in invite call:", err)
|
||||||
cmd.Reply("Failed to invite user: %v", err)
|
cmd.Reply("Failed to invite user: %v", err)
|
||||||
@ -392,7 +394,7 @@ func cmdBan(cmd *Command) {
|
|||||||
if len(cmd.Args) >= 2 {
|
if len(cmd.Args) >= 2 {
|
||||||
reason = strings.Join(cmd.Args[1:], " ")
|
reason = strings.Join(cmd.Args[1:], " ")
|
||||||
}
|
}
|
||||||
_, err := cmd.Matrix.Client().BanUser(cmd.Room.MxRoom().ID, &mautrix.ReqBanUser{Reason: reason, UserID: cmd.Args[0]})
|
_, err := cmd.Matrix.Client().BanUser(cmd.Room.MxRoom().ID, &mautrix.ReqBanUser{Reason: reason, UserID: id.UserID(cmd.Args[0])})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Print("Error in ban call:", err)
|
debug.Print("Error in ban call:", err)
|
||||||
cmd.Reply("Failed to ban user: %v", err)
|
cmd.Reply("Failed to ban user: %v", err)
|
||||||
@ -405,7 +407,7 @@ func cmdUnban(cmd *Command) {
|
|||||||
cmd.Reply("Usage: /unban <user>")
|
cmd.Reply("Usage: /unban <user>")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, err := cmd.Matrix.Client().UnbanUser(cmd.Room.MxRoom().ID, &mautrix.ReqUnbanUser{UserID: cmd.Args[0]})
|
_, err := cmd.Matrix.Client().UnbanUser(cmd.Room.MxRoom().ID, &mautrix.ReqUnbanUser{UserID: id.UserID(cmd.Args[0])})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Print("Error in unban call:", err)
|
debug.Print("Error in unban call:", err)
|
||||||
cmd.Reply("Failed to unban user: %v", err)
|
cmd.Reply("Failed to unban user: %v", err)
|
||||||
@ -421,7 +423,7 @@ func cmdKick(cmd *Command) {
|
|||||||
if len(cmd.Args) >= 2 {
|
if len(cmd.Args) >= 2 {
|
||||||
reason = strings.Join(cmd.Args[1:], " ")
|
reason = strings.Join(cmd.Args[1:], " ")
|
||||||
}
|
}
|
||||||
_, err := cmd.Matrix.Client().KickUser(cmd.Room.MxRoom().ID, &mautrix.ReqKickUser{Reason: reason, UserID: cmd.Args[0]})
|
_, err := cmd.Matrix.Client().KickUser(cmd.Room.MxRoom().ID, &mautrix.ReqKickUser{Reason: reason, UserID: id.UserID(cmd.Args[0])})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Print("Error in kick call:", err)
|
debug.Print("Error in kick call:", err)
|
||||||
debug.Print("Failed to kick user:", err)
|
debug.Print("Failed to kick user:", err)
|
||||||
@ -445,9 +447,18 @@ func cmdPrivateMessage(cmd *Command) {
|
|||||||
if len(cmd.Args) == 0 {
|
if len(cmd.Args) == 0 {
|
||||||
cmd.Reply("Usage: /pm <user id> [more user ids...]")
|
cmd.Reply("Usage: /pm <user id> [more user ids...]")
|
||||||
}
|
}
|
||||||
|
invites := make([]id.UserID, len(cmd.Args))
|
||||||
|
for i, userID := range cmd.Args {
|
||||||
|
invites[i] = id.UserID(userID)
|
||||||
|
_, _, err := invites[i].Parse()
|
||||||
|
if err != nil {
|
||||||
|
cmd.Reply("%s isn't a valid user ID", userID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
req := &mautrix.ReqCreateRoom{
|
req := &mautrix.ReqCreateRoom{
|
||||||
Preset: "trusted_private_chat",
|
Preset: "trusted_private_chat",
|
||||||
Invite: cmd.Args,
|
Invite: invites,
|
||||||
}
|
}
|
||||||
room, err := cmd.Matrix.CreateRoom(req)
|
room, err := cmd.Matrix.CreateRoom(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -462,7 +473,7 @@ func cmdJoin(cmd *Command) {
|
|||||||
cmd.Reply("Usage: /join <room>")
|
cmd.Reply("Usage: /join <room>")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
identifer := cmd.Args[0]
|
identifer := id.RoomID(cmd.Args[0])
|
||||||
server := ""
|
server := ""
|
||||||
if len(cmd.Args) > 1 {
|
if len(cmd.Args) > 1 {
|
||||||
server = cmd.Args[1]
|
server = cmd.Args[1]
|
||||||
@ -479,7 +490,7 @@ func cmdMSendEvent(cmd *Command) {
|
|||||||
cmd.Reply("Usage: /msend <event type> <content>")
|
cmd.Reply("Usage: /msend <event type> <content>")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cmd.Args = append([]string{cmd.Room.MxRoom().ID}, cmd.Args...)
|
cmd.Args = append([]string{string(cmd.Room.MxRoom().ID)}, cmd.Args...)
|
||||||
cmdSendEvent(cmd)
|
cmdSendEvent(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -488,8 +499,8 @@ func cmdSendEvent(cmd *Command) {
|
|||||||
cmd.Reply("Usage: /send <room id> <event type> <content>")
|
cmd.Reply("Usage: /send <room id> <event type> <content>")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
roomID := cmd.Args[0]
|
roomID := id.RoomID(cmd.Args[0])
|
||||||
eventType := mautrix.NewEventType(cmd.Args[1])
|
eventType := event.NewEventType(cmd.Args[1])
|
||||||
rawContent := strings.Join(cmd.Args[2:], " ")
|
rawContent := strings.Join(cmd.Args[2:], " ")
|
||||||
|
|
||||||
var content interface{}
|
var content interface{}
|
||||||
@ -515,7 +526,7 @@ func cmdMSetState(cmd *Command) {
|
|||||||
cmd.Reply("Usage: /msetstate <event type> <state key> <content>")
|
cmd.Reply("Usage: /msetstate <event type> <state key> <content>")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cmd.Args = append([]string{cmd.Room.MxRoom().ID}, cmd.Args...)
|
cmd.Args = append([]string{string(cmd.Room.MxRoom().ID)}, cmd.Args...)
|
||||||
cmdSetState(cmd)
|
cmdSetState(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -525,8 +536,8 @@ func cmdSetState(cmd *Command) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
roomID := cmd.Args[0]
|
roomID := id.RoomID(cmd.Args[0])
|
||||||
eventType := mautrix.NewEventType(cmd.Args[1])
|
eventType := event.NewEventType(cmd.Args[1])
|
||||||
stateKey := cmd.Args[2]
|
stateKey := cmd.Args[2]
|
||||||
if stateKey == "-" {
|
if stateKey == "-" {
|
||||||
stateKey = ""
|
stateKey = ""
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
|
|
||||||
"github.com/lithammer/fuzzysearch/fuzzy"
|
"github.com/lithammer/fuzzysearch/fuzzy"
|
||||||
|
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
"maunium.net/go/mauview"
|
"maunium.net/go/mauview"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
|
|
||||||
@ -87,7 +88,7 @@ func (fs *FuzzySearchModal) Blur() {
|
|||||||
fs.container.Blur()
|
fs.container.Blur()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FuzzySearchModal) InitList(rooms map[string]*RoomView) {
|
func (fs *FuzzySearchModal) InitList(rooms map[id.RoomID]*RoomView) {
|
||||||
for _, room := range rooms {
|
for _, room := range rooms {
|
||||||
if room.Room.IsReplaced() {
|
if room.Room.IsReplaced() {
|
||||||
//if _, ok := rooms[room.Room.ReplacedBy()]; ok
|
//if _, ok := rooms[room.Room.ReplacedBy()]; ok
|
||||||
|
@ -23,7 +23,8 @@ import (
|
|||||||
|
|
||||||
"github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
|
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix/event"
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
"maunium.net/go/mauview"
|
"maunium.net/go/mauview"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
|
|
||||||
@ -43,7 +44,7 @@ type memberListItem struct {
|
|||||||
rooms.Member
|
rooms.Member
|
||||||
PowerLevel int
|
PowerLevel int
|
||||||
Sigil rune
|
Sigil rune
|
||||||
UserID string
|
UserID id.UserID
|
||||||
Color tcell.Color
|
Color tcell.Color
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +65,7 @@ func (rml roomMemberList) Swap(i, j int) {
|
|||||||
rml[i], rml[j] = rml[j], rml[i]
|
rml[i], rml[j] = rml[j], rml[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ml *MemberList) Update(data map[string]*rooms.Member, levels *mautrix.PowerLevels) *MemberList {
|
func (ml *MemberList) Update(data map[id.UserID]*rooms.Member, levels *event.PowerLevels) *MemberList {
|
||||||
ml.list = make(roomMemberList, len(data))
|
ml.list = make(roomMemberList, len(data))
|
||||||
i := 0
|
i := 0
|
||||||
highestLevel := math.MinInt32
|
highestLevel := math.MinInt32
|
||||||
|
@ -25,10 +25,12 @@ import (
|
|||||||
"github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
sync "github.com/sasha-s/go-deadlock"
|
sync "github.com/sasha-s/go-deadlock"
|
||||||
|
|
||||||
"maunium.net/go/mautrix"
|
|
||||||
"maunium.net/go/mauview"
|
"maunium.net/go/mauview"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
|
|
||||||
|
"maunium.net/go/mautrix/event"
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/config"
|
"maunium.net/go/gomuks/config"
|
||||||
"maunium.net/go/gomuks/debug"
|
"maunium.net/go/gomuks/debug"
|
||||||
"maunium.net/go/gomuks/interface"
|
"maunium.net/go/gomuks/interface"
|
||||||
@ -59,7 +61,7 @@ type MessageView struct {
|
|||||||
prevPrefs config.UserPreferences
|
prevPrefs config.UserPreferences
|
||||||
|
|
||||||
messageIDLock sync.RWMutex
|
messageIDLock sync.RWMutex
|
||||||
messageIDs map[string]*messages.UIMessage
|
messageIDs map[id.EventID]*messages.UIMessage
|
||||||
messagesLock sync.RWMutex
|
messagesLock sync.RWMutex
|
||||||
messages []*messages.UIMessage
|
messages []*messages.UIMessage
|
||||||
msgBufferLock sync.RWMutex
|
msgBufferLock sync.RWMutex
|
||||||
@ -79,7 +81,7 @@ func NewMessageView(parent *RoomView) *MessageView {
|
|||||||
ScrollOffset: 0,
|
ScrollOffset: 0,
|
||||||
|
|
||||||
messages: make([]*messages.UIMessage, 0),
|
messages: make([]*messages.UIMessage, 0),
|
||||||
messageIDs: make(map[string]*messages.UIMessage),
|
messageIDs: make(map[id.EventID]*messages.UIMessage),
|
||||||
msgBuffer: make([]*messages.UIMessage, 0),
|
msgBuffer: make([]*messages.UIMessage, 0),
|
||||||
|
|
||||||
_width: 80,
|
_width: 80,
|
||||||
@ -95,7 +97,7 @@ func (view *MessageView) Unload() {
|
|||||||
view.messagesLock.Lock()
|
view.messagesLock.Lock()
|
||||||
view.msgBufferLock.Lock()
|
view.msgBufferLock.Lock()
|
||||||
view.messageIDLock.Lock()
|
view.messageIDLock.Lock()
|
||||||
view.messageIDs = make(map[string]*messages.UIMessage)
|
view.messageIDs = make(map[id.EventID]*messages.UIMessage)
|
||||||
view.msgBuffer = make([]*messages.UIMessage, 0)
|
view.msgBuffer = make([]*messages.UIMessage, 0)
|
||||||
view.messages = make([]*messages.UIMessage, 0)
|
view.messages = make([]*messages.UIMessage, 0)
|
||||||
view.initialHistoryLoaded = false
|
view.initialHistoryLoaded = false
|
||||||
@ -140,9 +142,9 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction MessageDir
|
|||||||
if oldMsg = view.getMessageByID(message.EventID); oldMsg != nil {
|
if oldMsg = view.getMessageByID(message.EventID); oldMsg != nil {
|
||||||
view.replaceMessage(oldMsg, message)
|
view.replaceMessage(oldMsg, message)
|
||||||
direction = IgnoreMessage
|
direction = IgnoreMessage
|
||||||
} else if oldMsg = view.getMessageByID(message.TxnID); oldMsg != nil {
|
} else if oldMsg = view.getMessageByID(id.EventID(message.TxnID)); oldMsg != nil {
|
||||||
view.replaceMessage(oldMsg, message)
|
view.replaceMessage(oldMsg, message)
|
||||||
view.deleteMessageID(message.TxnID)
|
view.deleteMessageID(id.EventID(message.TxnID))
|
||||||
direction = IgnoreMessage
|
direction = IgnoreMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,7 +210,7 @@ func (view *MessageView) replaceMessage(original *messages.UIMessage, new *messa
|
|||||||
view.messagesLock.Unlock()
|
view.messagesLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MessageView) getMessageByID(id string) *messages.UIMessage {
|
func (view *MessageView) getMessageByID(id id.EventID) *messages.UIMessage {
|
||||||
if id == "" {
|
if id == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -221,7 +223,7 @@ func (view *MessageView) getMessageByID(id string) *messages.UIMessage {
|
|||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MessageView) deleteMessageID(id string) {
|
func (view *MessageView) deleteMessageID(id id.EventID) {
|
||||||
if id == "" {
|
if id == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -365,7 +367,7 @@ func (view *MessageView) handleUsernameClick(message *messages.UIMessage, prevMe
|
|||||||
// return false
|
// return false
|
||||||
//}
|
//}
|
||||||
|
|
||||||
if message.SenderName == "---" || message.SenderName == "-->" || message.SenderName == "<--" || message.Type == mautrix.MsgEmote {
|
if message.SenderName == "---" || message.SenderName == "-->" || message.SenderName == "<--" || message.Type == event.MsgEmote {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -564,7 +566,7 @@ func (view *MessageView) CapturePlaintext(height int) string {
|
|||||||
var sender string
|
var sender string
|
||||||
if len(message.Sender()) > 0 {
|
if len(message.Sender()) > 0 {
|
||||||
sender = fmt.Sprintf(" <%s>", message.Sender())
|
sender = fmt.Sprintf(" <%s>", message.Sender())
|
||||||
} else if message.Type == mautrix.MsgEmote {
|
} else if message.Type == event.MsgEmote {
|
||||||
sender = fmt.Sprintf(" * %s", message.SenderName)
|
sender = fmt.Sprintf(" * %s", message.SenderName)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(&buf, "%s%s %s\n", message.FormatTime(), sender, message.PlainText())
|
fmt.Fprintf(&buf, "%s%s %s\n", message.FormatTime(), sender, message.PlainText())
|
||||||
|
@ -22,8 +22,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/config"
|
"maunium.net/go/gomuks/config"
|
||||||
"maunium.net/go/gomuks/matrix/event"
|
"maunium.net/go/gomuks/matrix/muksevt"
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix/event"
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
"maunium.net/go/mauview"
|
"maunium.net/go/mauview"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
|
|
||||||
@ -64,26 +65,26 @@ func (rs ReactionSlice) Swap(i, j int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UIMessage struct {
|
type UIMessage struct {
|
||||||
EventID string
|
EventID id.EventID
|
||||||
TxnID string
|
TxnID string
|
||||||
Relation mautrix.RelatesTo
|
Relation event.RelatesTo
|
||||||
Type mautrix.MessageType
|
Type event.MessageType
|
||||||
SenderID string
|
SenderID id.UserID
|
||||||
SenderName string
|
SenderName string
|
||||||
DefaultSenderColor tcell.Color
|
DefaultSenderColor tcell.Color
|
||||||
Timestamp time.Time
|
Timestamp time.Time
|
||||||
State event.OutgoingState
|
State muksevt.OutgoingState
|
||||||
IsHighlight bool
|
IsHighlight bool
|
||||||
IsService bool
|
IsService bool
|
||||||
IsSelected bool
|
IsSelected bool
|
||||||
Edited bool
|
Edited bool
|
||||||
Event *event.Event
|
Event *muksevt.Event
|
||||||
ReplyTo *UIMessage
|
ReplyTo *UIMessage
|
||||||
Reactions ReactionSlice
|
Reactions ReactionSlice
|
||||||
Renderer MessageRenderer
|
Renderer MessageRenderer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *UIMessage) GetEvent() *event.Event {
|
func (msg *UIMessage) GetEvent() *muksevt.Event {
|
||||||
if msg == nil {
|
if msg == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -93,10 +94,10 @@ func (msg *UIMessage) GetEvent() *event.Event {
|
|||||||
const DateFormat = "January _2, 2006"
|
const DateFormat = "January _2, 2006"
|
||||||
const TimeFormat = "15:04:05"
|
const TimeFormat = "15:04:05"
|
||||||
|
|
||||||
func newUIMessage(evt *event.Event, displayname string, renderer MessageRenderer) *UIMessage {
|
func newUIMessage(evt *muksevt.Event, displayname string, renderer MessageRenderer) *UIMessage {
|
||||||
msgtype := evt.Content.MsgType
|
msgtype := evt.Content.MsgType
|
||||||
if len(msgtype) == 0 {
|
if len(msgtype) == 0 {
|
||||||
msgtype = mautrix.MessageType(evt.Type.String())
|
msgtype = event.MessageType(evt.Type.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
reactions := make(ReactionSlice, 0, len(evt.Unsigned.Relations.Annotations.Map))
|
reactions := make(ReactionSlice, 0, len(evt.Unsigned.Relations.Annotations.Map))
|
||||||
@ -161,9 +162,9 @@ func unixToTime(unix int64) time.Time {
|
|||||||
// In any other case, the sender is the display name of the user who sent the message.
|
// In any other case, the sender is the display name of the user who sent the message.
|
||||||
func (msg *UIMessage) Sender() string {
|
func (msg *UIMessage) Sender() string {
|
||||||
switch msg.State {
|
switch msg.State {
|
||||||
case event.StateLocalEcho:
|
case muksevt.StateLocalEcho:
|
||||||
return "Sending..."
|
return "Sending..."
|
||||||
case event.StateSendFail:
|
case muksevt.StateSendFail:
|
||||||
return "Error"
|
return "Error"
|
||||||
}
|
}
|
||||||
switch msg.Type {
|
switch msg.Type {
|
||||||
@ -185,11 +186,11 @@ func (msg *UIMessage) NotificationContent() string {
|
|||||||
|
|
||||||
func (msg *UIMessage) getStateSpecificColor() tcell.Color {
|
func (msg *UIMessage) getStateSpecificColor() tcell.Color {
|
||||||
switch msg.State {
|
switch msg.State {
|
||||||
case event.StateLocalEcho:
|
case muksevt.StateLocalEcho:
|
||||||
return tcell.ColorGray
|
return tcell.ColorGray
|
||||||
case event.StateSendFail:
|
case muksevt.StateSendFail:
|
||||||
return tcell.ColorRed
|
return tcell.ColorRed
|
||||||
case event.StateDefault:
|
case muksevt.StateDefault:
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
return tcell.ColorDefault
|
return tcell.ColorDefault
|
||||||
@ -286,14 +287,14 @@ func (msg *UIMessage) SameDate(message *UIMessage) bool {
|
|||||||
return day1 == day2 && month1 == month2 && year1 == year2
|
return day1 == day2 && month1 == month2 && year1 == year2
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *UIMessage) ID() string {
|
func (msg *UIMessage) ID() id.EventID {
|
||||||
if len(msg.EventID) == 0 {
|
if len(msg.EventID) == 0 {
|
||||||
return msg.TxnID
|
return id.EventID(msg.TxnID)
|
||||||
}
|
}
|
||||||
return msg.EventID
|
return msg.EventID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *UIMessage) SetID(id string) {
|
func (msg *UIMessage) SetID(id id.EventID) {
|
||||||
msg.EventID = id
|
msg.EventID = id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/matrix/event"
|
"maunium.net/go/gomuks/matrix/muksevt"
|
||||||
"maunium.net/go/mauview"
|
"maunium.net/go/mauview"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ type ExpandedTextMessage struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewExpandedTextMessage creates a new ExpandedTextMessage object with the provided values and the default state.
|
// NewExpandedTextMessage creates a new ExpandedTextMessage object with the provided values and the default state.
|
||||||
func NewExpandedTextMessage(evt *event.Event, displayname string, text tstring.TString) *UIMessage {
|
func NewExpandedTextMessage(evt *muksevt.Event, displayname string, text tstring.TString) *UIMessage {
|
||||||
return newUIMessage(evt, displayname, &ExpandedTextMessage{
|
return newUIMessage(evt, displayname, &ExpandedTextMessage{
|
||||||
Text: text,
|
Text: text,
|
||||||
})
|
})
|
||||||
|
@ -22,8 +22,9 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/matrix/event"
|
"maunium.net/go/gomuks/matrix/muksevt"
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix/event"
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
"maunium.net/go/mauview"
|
"maunium.net/go/mauview"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
|
|
||||||
@ -35,10 +36,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type FileMessage struct {
|
type FileMessage struct {
|
||||||
Type mautrix.MessageType
|
Type event.MessageType
|
||||||
Body string
|
Body string
|
||||||
URL mautrix.ContentURI
|
URL id.ContentURI
|
||||||
Thumbnail mautrix.ContentURI
|
Thumbnail id.ContentURI
|
||||||
imageData []byte
|
imageData []byte
|
||||||
buffer []tstring.TString
|
buffer []tstring.TString
|
||||||
|
|
||||||
@ -46,9 +47,9 @@ type FileMessage struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewFileMessage creates a new FileMessage object with the provided values and the default state.
|
// NewFileMessage creates a new FileMessage object with the provided values and the default state.
|
||||||
func NewFileMessage(matrix ifc.MatrixContainer, evt *event.Event, displayname string) *UIMessage {
|
func NewFileMessage(matrix ifc.MatrixContainer, evt *muksevt.Event, displayname string) *UIMessage {
|
||||||
url, _ := mautrix.ParseContentURI(evt.Content.URL)
|
url, _ := evt.Content.URL.Parse()
|
||||||
thumbnail, _ := mautrix.ParseContentURI(evt.Content.GetInfo().ThumbnailURL)
|
thumbnail, _ := evt.Content.GetInfo().ThumbnailURL.Parse()
|
||||||
return newUIMessage(evt, displayname, &FileMessage{
|
return newUIMessage(evt, displayname, &FileMessage{
|
||||||
Type: evt.Content.MsgType,
|
Type: evt.Content.MsgType,
|
||||||
Body: evt.Content.Body,
|
Body: evt.Content.Body,
|
||||||
@ -72,13 +73,13 @@ func (msg *FileMessage) Clone() MessageRenderer {
|
|||||||
|
|
||||||
func (msg *FileMessage) NotificationContent() string {
|
func (msg *FileMessage) NotificationContent() string {
|
||||||
switch msg.Type {
|
switch msg.Type {
|
||||||
case mautrix.MsgImage:
|
case event.MsgImage:
|
||||||
return "Sent an image"
|
return "Sent an image"
|
||||||
case mautrix.MsgAudio:
|
case event.MsgAudio:
|
||||||
return "Sent an audio file"
|
return "Sent an audio file"
|
||||||
case mautrix.MsgVideo:
|
case event.MsgVideo:
|
||||||
return "Sent a video"
|
return "Sent a video"
|
||||||
case mautrix.MsgFile:
|
case event.MsgFile:
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
return "Sent a file"
|
return "Sent a file"
|
||||||
@ -96,7 +97,7 @@ func (msg *FileMessage) String() string {
|
|||||||
func (msg *FileMessage) DownloadPreview() {
|
func (msg *FileMessage) DownloadPreview() {
|
||||||
url := msg.Thumbnail
|
url := msg.Thumbnail
|
||||||
if url.IsEmpty() {
|
if url.IsEmpty() {
|
||||||
if msg.Type == mautrix.MsgImage && !msg.URL.IsEmpty() {
|
if msg.Type == event.MsgImage && !msg.URL.IsEmpty() {
|
||||||
msg.Thumbnail = msg.URL
|
msg.Thumbnail = msg.URL
|
||||||
url = msg.Thumbnail
|
url = msg.Thumbnail
|
||||||
} else {
|
} else {
|
||||||
|
@ -27,8 +27,9 @@ import (
|
|||||||
"github.com/lucasb-eyer/go-colorful"
|
"github.com/lucasb-eyer/go-colorful"
|
||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/matrix/event"
|
"maunium.net/go/gomuks/matrix/muksevt"
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix/event"
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
@ -185,12 +186,12 @@ func (parser *htmlParser) linkToEntity(node *html.Node) Entity {
|
|||||||
pillTarget := match[1]
|
pillTarget := match[1]
|
||||||
text := NewTextEntity(pillTarget)
|
text := NewTextEntity(pillTarget)
|
||||||
if pillTarget[0] == '@' {
|
if pillTarget[0] == '@' {
|
||||||
if member := parser.room.GetMember(pillTarget); member != nil {
|
if member := parser.room.GetMember(id.UserID(pillTarget)); member != nil {
|
||||||
text.Text = member.Displayname
|
text.Text = member.Displayname
|
||||||
text.Style = text.Style.Foreground(widget.GetHashColor(pillTarget))
|
text.Style = text.Style.Foreground(widget.GetHashColor(pillTarget))
|
||||||
}
|
}
|
||||||
entity.Children = []Entity{text}
|
entity.Children = []Entity{text}
|
||||||
/*} else if slash := strings.IndexRune(pillTarget, '/'); slash != -1 {
|
/*} else if slash := strings.IndexRune(pillTarget, '/'); slash != -1 {
|
||||||
room := pillTarget[:slash]
|
room := pillTarget[:slash]
|
||||||
event := pillTarget[slash+1:]*/
|
event := pillTarget[slash+1:]*/
|
||||||
} else if pillTarget[0] == '#' {
|
} else if pillTarget[0] == '#' {
|
||||||
@ -383,9 +384,9 @@ func (parser *htmlParser) Parse(htmlData string) Entity {
|
|||||||
const TabLength = 4
|
const TabLength = 4
|
||||||
|
|
||||||
// Parse parses a HTML-formatted Matrix event into a UIMessage.
|
// Parse parses a HTML-formatted Matrix event into a UIMessage.
|
||||||
func Parse(room *rooms.Room, evt *event.Event, senderDisplayname string) Entity {
|
func Parse(room *rooms.Room, evt *muksevt.Event, senderDisplayname string) Entity {
|
||||||
htmlData := evt.Content.FormattedBody
|
htmlData := evt.Content.FormattedBody
|
||||||
if evt.Content.Format != mautrix.FormatHTML {
|
if evt.Content.Format != event.FormatHTML {
|
||||||
htmlData = strings.Replace(html.EscapeString(evt.Content.Body), "\n", "<br/>", -1)
|
htmlData = strings.Replace(html.EscapeString(evt.Content.Body), "\n", "<br/>", -1)
|
||||||
}
|
}
|
||||||
htmlData = strings.Replace(htmlData, "\t", strings.Repeat(" ", TabLength), -1)
|
htmlData = strings.Replace(htmlData, "\t", strings.Repeat(" ", TabLength), -1)
|
||||||
@ -402,7 +403,7 @@ func Parse(room *rooms.Room, evt *event.Event, senderDisplayname string) Entity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if evt.Content.MsgType == mautrix.MsgEmote {
|
if evt.Content.MsgType == event.MsgEmote {
|
||||||
root = &ContainerEntity{
|
root = &ContainerEntity{
|
||||||
BaseEntity: &BaseEntity{
|
BaseEntity: &BaseEntity{
|
||||||
Tag: "emote",
|
Tag: "emote",
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
package messages
|
package messages
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"maunium.net/go/gomuks/matrix/event"
|
"maunium.net/go/gomuks/matrix/muksevt"
|
||||||
"maunium.net/go/mauview"
|
"maunium.net/go/mauview"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ type HTMLMessage struct {
|
|||||||
focused bool
|
focused bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTMLMessage(evt *event.Event, displayname string, root html.Entity) *UIMessage {
|
func NewHTMLMessage(evt *muksevt.Event, displayname string, root html.Entity) *UIMessage {
|
||||||
return newUIMessage(evt, displayname, &HTMLMessage{
|
return newUIMessage(evt, displayname, &HTMLMessage{
|
||||||
Root: root,
|
Root: root,
|
||||||
})
|
})
|
||||||
|
@ -20,8 +20,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/matrix/event"
|
"maunium.net/go/gomuks/matrix/muksevt"
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix/event"
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/interface"
|
"maunium.net/go/gomuks/interface"
|
||||||
@ -31,7 +32,7 @@ import (
|
|||||||
"maunium.net/go/gomuks/ui/widget"
|
"maunium.net/go/gomuks/ui/widget"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getCachedEvent(mainView ifc.MainView, roomID, eventID string) *UIMessage {
|
func getCachedEvent(mainView ifc.MainView, roomID id.RoomID, eventID id.EventID) *UIMessage {
|
||||||
if roomView := mainView.GetRoom(roomID); roomView != nil {
|
if roomView := mainView.GetRoom(roomID); roomView != nil {
|
||||||
if replyToIfcMsg := roomView.GetEvent(eventID); replyToIfcMsg != nil {
|
if replyToIfcMsg := roomView.GetEvent(eventID); replyToIfcMsg != nil {
|
||||||
if replyToMsg, ok := replyToIfcMsg.(*UIMessage); ok && replyToMsg != nil {
|
if replyToMsg, ok := replyToIfcMsg.(*UIMessage); ok && replyToMsg != nil {
|
||||||
@ -42,7 +43,7 @@ func getCachedEvent(mainView ifc.MainView, roomID, eventID string) *UIMessage {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseEvent(matrix ifc.MatrixContainer, mainView ifc.MainView, room *rooms.Room, evt *event.Event) *UIMessage {
|
func ParseEvent(matrix ifc.MatrixContainer, mainView ifc.MainView, room *rooms.Room, evt *muksevt.Event) *UIMessage {
|
||||||
msg := directParseEvent(matrix, room, evt)
|
msg := directParseEvent(matrix, room, evt)
|
||||||
if msg == nil {
|
if msg == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -64,36 +65,36 @@ func ParseEvent(matrix ifc.MatrixContainer, mainView ifc.MainView, room *rooms.R
|
|||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
func directParseEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *event.Event) *UIMessage {
|
func directParseEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *muksevt.Event) *UIMessage {
|
||||||
displayname := evt.Sender
|
displayname := string(evt.Sender)
|
||||||
member := room.GetMember(evt.Sender)
|
member := room.GetMember(evt.Sender)
|
||||||
if member != nil {
|
if member != nil {
|
||||||
displayname = member.Displayname
|
displayname = member.Displayname
|
||||||
}
|
}
|
||||||
if evt.Unsigned.RedactedBecause != nil || evt.Type == mautrix.EventRedaction {
|
if evt.Unsigned.RedactedBecause != nil || evt.Type == event.EventRedaction {
|
||||||
return NewRedactedMessage(evt, displayname)
|
return NewRedactedMessage(evt, displayname)
|
||||||
}
|
}
|
||||||
switch evt.Type {
|
switch evt.Type {
|
||||||
case mautrix.EventSticker:
|
case event.EventSticker:
|
||||||
evt.Content.MsgType = mautrix.MsgImage
|
evt.Content.MsgType = event.MsgImage
|
||||||
fallthrough
|
fallthrough
|
||||||
case mautrix.EventMessage:
|
case event.EventMessage:
|
||||||
return ParseMessage(matrix, room, evt, displayname)
|
return ParseMessage(matrix, room, evt, displayname)
|
||||||
case mautrix.EventEncrypted:
|
case event.EventEncrypted:
|
||||||
return NewExpandedTextMessage(evt, displayname, tstring.NewStyleTString("Encrypted messages are not yet supported", tcell.StyleDefault.Italic(true)))
|
return NewExpandedTextMessage(evt, displayname, tstring.NewStyleTString("Encrypted messages are not yet supported", tcell.StyleDefault.Italic(true)))
|
||||||
case mautrix.StateTopic, mautrix.StateRoomName, mautrix.StateAliases, mautrix.StateCanonicalAlias:
|
case event.StateTopic, event.StateRoomName, event.StateAliases, event.StateCanonicalAlias:
|
||||||
return ParseStateEvent(evt, displayname)
|
return ParseStateEvent(evt, displayname)
|
||||||
case mautrix.StateMember:
|
case event.StateMember:
|
||||||
return ParseMembershipEvent(room, evt)
|
return ParseMembershipEvent(room, evt)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseStateEvent(evt *event.Event, displayname string) *UIMessage {
|
func ParseStateEvent(evt *muksevt.Event, displayname string) *UIMessage {
|
||||||
text := tstring.NewColorTString(displayname, widget.GetHashColor(evt.Sender))
|
text := tstring.NewColorTString(displayname, widget.GetHashColor(evt.Sender))
|
||||||
switch evt.Type {
|
switch evt.Type {
|
||||||
case mautrix.StateTopic:
|
case event.StateTopic:
|
||||||
if len(evt.Content.Topic) == 0 {
|
if len(evt.Content.Topic) == 0 {
|
||||||
text = text.AppendColor(" removed the topic.", tcell.ColorGreen)
|
text = text.AppendColor(" removed the topic.", tcell.ColorGreen)
|
||||||
} else {
|
} else {
|
||||||
@ -101,7 +102,7 @@ func ParseStateEvent(evt *event.Event, displayname string) *UIMessage {
|
|||||||
AppendStyle(evt.Content.Topic, tcell.StyleDefault.Underline(true)).
|
AppendStyle(evt.Content.Topic, tcell.StyleDefault.Underline(true)).
|
||||||
AppendColor(".", tcell.ColorGreen)
|
AppendColor(".", tcell.ColorGreen)
|
||||||
}
|
}
|
||||||
case mautrix.StateRoomName:
|
case event.StateRoomName:
|
||||||
if len(evt.Content.Name) == 0 {
|
if len(evt.Content.Name) == 0 {
|
||||||
text = text.AppendColor(" removed the room name.", tcell.ColorGreen)
|
text = text.AppendColor(" removed the room name.", tcell.ColorGreen)
|
||||||
} else {
|
} else {
|
||||||
@ -109,21 +110,21 @@ func ParseStateEvent(evt *event.Event, displayname string) *UIMessage {
|
|||||||
AppendStyle(evt.Content.Name, tcell.StyleDefault.Underline(true)).
|
AppendStyle(evt.Content.Name, tcell.StyleDefault.Underline(true)).
|
||||||
AppendColor(".", tcell.ColorGreen)
|
AppendColor(".", tcell.ColorGreen)
|
||||||
}
|
}
|
||||||
case mautrix.StateCanonicalAlias:
|
case event.StateCanonicalAlias:
|
||||||
if len(evt.Content.Alias) == 0 {
|
if len(evt.Content.Alias) == 0 {
|
||||||
text = text.AppendColor(" removed the main address of the room.", tcell.ColorGreen)
|
text = text.AppendColor(" removed the main address of the room.", tcell.ColorGreen)
|
||||||
} else {
|
} else {
|
||||||
text = text.AppendColor(" changed the main address of the room to ", tcell.ColorGreen).
|
text = text.AppendColor(" changed the main address of the room to ", tcell.ColorGreen).
|
||||||
AppendStyle(evt.Content.Alias, tcell.StyleDefault.Underline(true)).
|
AppendStyle(string(evt.Content.Alias), tcell.StyleDefault.Underline(true)).
|
||||||
AppendColor(".", tcell.ColorGreen)
|
AppendColor(".", tcell.ColorGreen)
|
||||||
}
|
}
|
||||||
case mautrix.StateAliases:
|
//case event.StateAliases:
|
||||||
text = ParseAliasEvent(evt, displayname)
|
// text = ParseAliasEvent(evt, displayname)
|
||||||
}
|
}
|
||||||
return NewExpandedTextMessage(evt, displayname, text)
|
return NewExpandedTextMessage(evt, displayname, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseMessage(matrix ifc.MatrixContainer, room *rooms.Room, evt *event.Event, displayname string) *UIMessage {
|
func ParseMessage(matrix ifc.MatrixContainer, room *rooms.Room, evt *muksevt.Event, displayname string) *UIMessage {
|
||||||
if len(evt.Content.GetReplyTo()) > 0 {
|
if len(evt.Content.GetReplyTo()) > 0 {
|
||||||
evt.Content.RemoveReplyFallback()
|
evt.Content.RemoveReplyFallback()
|
||||||
}
|
}
|
||||||
@ -131,13 +132,13 @@ func ParseMessage(matrix ifc.MatrixContainer, room *rooms.Room, evt *event.Event
|
|||||||
evt.Content = *evt.Gomuks.Edits[len(evt.Gomuks.Edits)-1].Content.NewContent
|
evt.Content = *evt.Gomuks.Edits[len(evt.Gomuks.Edits)-1].Content.NewContent
|
||||||
}
|
}
|
||||||
switch evt.Content.MsgType {
|
switch evt.Content.MsgType {
|
||||||
case mautrix.MsgText, mautrix.MsgNotice, mautrix.MsgEmote:
|
case event.MsgText, event.MsgNotice, event.MsgEmote:
|
||||||
if evt.Content.Format == mautrix.FormatHTML {
|
if evt.Content.Format == event.FormatHTML {
|
||||||
return NewHTMLMessage(evt, displayname, html.Parse(room, evt, displayname))
|
return NewHTMLMessage(evt, displayname, html.Parse(room, evt, displayname))
|
||||||
}
|
}
|
||||||
evt.Content.Body = strings.Replace(evt.Content.Body, "\t", " ", -1)
|
evt.Content.Body = strings.Replace(evt.Content.Body, "\t", " ", -1)
|
||||||
return NewTextMessage(evt, displayname, evt.Content.Body)
|
return NewTextMessage(evt, displayname, evt.Content.Body)
|
||||||
case mautrix.MsgImage, mautrix.MsgVideo, mautrix.MsgAudio, mautrix.MsgFile:
|
case event.MsgImage, event.MsgVideo, event.MsgAudio, event.MsgFile:
|
||||||
msg := NewFileMessage(matrix, evt, displayname)
|
msg := NewFileMessage(matrix, evt, displayname)
|
||||||
if !matrix.Preferences().DisableDownloads {
|
if !matrix.Preferences().DisableDownloads {
|
||||||
renderer := msg.Renderer.(*FileMessage)
|
renderer := msg.Renderer.(*FileMessage)
|
||||||
@ -148,54 +149,54 @@ func ParseMessage(matrix ifc.MatrixContainer, room *rooms.Room, evt *event.Event
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMembershipChangeMessage(evt *event.Event, membership, prevMembership mautrix.Membership, senderDisplayname, displayname, prevDisplayname string) (sender string, text tstring.TString) {
|
func getMembershipChangeMessage(evt *muksevt.Event, membership, prevMembership event.Membership, senderDisplayname, displayname, prevDisplayname string) (sender string, text tstring.TString) {
|
||||||
switch membership {
|
switch membership {
|
||||||
case "invite":
|
case "invite":
|
||||||
sender = "---"
|
sender = "---"
|
||||||
text = tstring.NewColorTString(fmt.Sprintf("%s invited %s.", senderDisplayname, displayname), tcell.ColorGreen)
|
text = tstring.NewColorTString(fmt.Sprintf("%s invited %s.", senderDisplayname, displayname), tcell.ColorGreen)
|
||||||
text.Colorize(0, len(senderDisplayname), widget.GetHashColor(evt.Sender))
|
text.Colorize(0, len(senderDisplayname), widget.GetHashColor(evt.Sender))
|
||||||
text.Colorize(len(senderDisplayname)+len(" invited "), len(displayname), widget.GetHashColor(*evt.StateKey))
|
text.Colorize(len(senderDisplayname)+len(" invited "), len(displayname), widget.GetHashColor(evt.StateKey))
|
||||||
case "join":
|
case "join":
|
||||||
sender = "-->"
|
sender = "-->"
|
||||||
if prevMembership == mautrix.MembershipInvite {
|
if prevMembership == event.MembershipInvite {
|
||||||
text = tstring.NewColorTString(fmt.Sprintf("%s accepted the invite.", displayname), tcell.ColorGreen)
|
text = tstring.NewColorTString(fmt.Sprintf("%s accepted the invite.", displayname), tcell.ColorGreen)
|
||||||
} else {
|
} else {
|
||||||
text = tstring.NewColorTString(fmt.Sprintf("%s joined the room.", displayname), tcell.ColorGreen)
|
text = tstring.NewColorTString(fmt.Sprintf("%s joined the room.", displayname), tcell.ColorGreen)
|
||||||
}
|
}
|
||||||
text.Colorize(0, len(displayname), widget.GetHashColor(*evt.StateKey))
|
text.Colorize(0, len(displayname), widget.GetHashColor(evt.StateKey))
|
||||||
case "leave":
|
case "leave":
|
||||||
sender = "<--"
|
sender = "<--"
|
||||||
if evt.Sender != *evt.StateKey {
|
if evt.Sender != id.UserID(*evt.StateKey) {
|
||||||
if prevMembership == mautrix.MembershipBan {
|
if prevMembership == event.MembershipBan {
|
||||||
text = tstring.NewColorTString(fmt.Sprintf("%s unbanned %s", senderDisplayname, displayname), tcell.ColorGreen)
|
text = tstring.NewColorTString(fmt.Sprintf("%s unbanned %s", senderDisplayname, displayname), tcell.ColorGreen)
|
||||||
text.Colorize(len(senderDisplayname)+len(" unbanned "), len(displayname), widget.GetHashColor(*evt.StateKey))
|
text.Colorize(len(senderDisplayname)+len(" unbanned "), len(displayname), widget.GetHashColor(evt.StateKey))
|
||||||
} else {
|
} else {
|
||||||
text = tstring.NewColorTString(fmt.Sprintf("%s kicked %s: %s", senderDisplayname, displayname, evt.Content.Reason), tcell.ColorRed)
|
text = tstring.NewColorTString(fmt.Sprintf("%s kicked %s: %s", senderDisplayname, displayname, evt.Content.Reason), tcell.ColorRed)
|
||||||
text.Colorize(len(senderDisplayname)+len(" kicked "), len(displayname), widget.GetHashColor(*evt.StateKey))
|
text.Colorize(len(senderDisplayname)+len(" kicked "), len(displayname), widget.GetHashColor(evt.StateKey))
|
||||||
}
|
}
|
||||||
text.Colorize(0, len(senderDisplayname), widget.GetHashColor(evt.Sender))
|
text.Colorize(0, len(senderDisplayname), widget.GetHashColor(evt.Sender))
|
||||||
} else {
|
} else {
|
||||||
if displayname == *evt.StateKey {
|
if displayname == *evt.StateKey {
|
||||||
displayname = prevDisplayname
|
displayname = prevDisplayname
|
||||||
}
|
}
|
||||||
if prevMembership == mautrix.MembershipInvite {
|
if prevMembership == event.MembershipInvite {
|
||||||
text = tstring.NewColorTString(fmt.Sprintf("%s rejected the invite.", displayname), tcell.ColorRed)
|
text = tstring.NewColorTString(fmt.Sprintf("%s rejected the invite.", displayname), tcell.ColorRed)
|
||||||
} else {
|
} else {
|
||||||
text = tstring.NewColorTString(fmt.Sprintf("%s left the room.", displayname), tcell.ColorRed)
|
text = tstring.NewColorTString(fmt.Sprintf("%s left the room.", displayname), tcell.ColorRed)
|
||||||
}
|
}
|
||||||
text.Colorize(0, len(displayname), widget.GetHashColor(*evt.StateKey))
|
text.Colorize(0, len(displayname), widget.GetHashColor(evt.StateKey))
|
||||||
}
|
}
|
||||||
case "ban":
|
case "ban":
|
||||||
text = tstring.NewColorTString(fmt.Sprintf("%s banned %s: %s", senderDisplayname, displayname, evt.Content.Reason), tcell.ColorRed)
|
text = tstring.NewColorTString(fmt.Sprintf("%s banned %s: %s", senderDisplayname, displayname, evt.Content.Reason), tcell.ColorRed)
|
||||||
text.Colorize(len(senderDisplayname)+len(" banned "), len(displayname), widget.GetHashColor(*evt.StateKey))
|
text.Colorize(len(senderDisplayname)+len(" banned "), len(displayname), widget.GetHashColor(evt.StateKey))
|
||||||
text.Colorize(0, len(senderDisplayname), widget.GetHashColor(evt.Sender))
|
text.Colorize(0, len(senderDisplayname), widget.GetHashColor(evt.Sender))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMembershipEventContent(room *rooms.Room, evt *event.Event) (sender string, text tstring.TString) {
|
func getMembershipEventContent(room *rooms.Room, evt *muksevt.Event) (sender string, text tstring.TString) {
|
||||||
member := room.GetMember(evt.Sender)
|
member := room.GetMember(evt.Sender)
|
||||||
senderDisplayname := evt.Sender
|
senderDisplayname := string(evt.Sender)
|
||||||
if member != nil {
|
if member != nil {
|
||||||
senderDisplayname = member.Displayname
|
senderDisplayname = member.Displayname
|
||||||
}
|
}
|
||||||
@ -206,7 +207,7 @@ func getMembershipEventContent(room *rooms.Room, evt *event.Event) (sender strin
|
|||||||
displayname = *evt.StateKey
|
displayname = *evt.StateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
prevMembership := mautrix.MembershipLeave
|
prevMembership := event.MembershipLeave
|
||||||
prevDisplayname := *evt.StateKey
|
prevDisplayname := *evt.StateKey
|
||||||
if evt.Unsigned.PrevContent != nil {
|
if evt.Unsigned.PrevContent != nil {
|
||||||
prevMembership = evt.Unsigned.PrevContent.Membership
|
prevMembership = evt.Unsigned.PrevContent.Membership
|
||||||
@ -220,7 +221,7 @@ func getMembershipEventContent(room *rooms.Room, evt *event.Event) (sender strin
|
|||||||
sender, text = getMembershipChangeMessage(evt, membership, prevMembership, senderDisplayname, displayname, prevDisplayname)
|
sender, text = getMembershipChangeMessage(evt, membership, prevMembership, senderDisplayname, displayname, prevDisplayname)
|
||||||
} else if displayname != prevDisplayname {
|
} else if displayname != prevDisplayname {
|
||||||
sender = "---"
|
sender = "---"
|
||||||
color := widget.GetHashColor(*evt.StateKey)
|
color := widget.GetHashColor(evt.StateKey)
|
||||||
text = tstring.NewBlankTString().
|
text = tstring.NewBlankTString().
|
||||||
AppendColor(prevDisplayname, color).
|
AppendColor(prevDisplayname, color).
|
||||||
AppendColor(" changed their display name to ", tcell.ColorGreen).
|
AppendColor(" changed their display name to ", tcell.ColorGreen).
|
||||||
@ -230,7 +231,7 @@ func getMembershipEventContent(room *rooms.Room, evt *event.Event) (sender strin
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseMembershipEvent(room *rooms.Room, evt *event.Event) *UIMessage {
|
func ParseMembershipEvent(room *rooms.Room, evt *muksevt.Event) *UIMessage {
|
||||||
displayname, text := getMembershipEventContent(room, evt)
|
displayname, text := getMembershipEventContent(room, evt)
|
||||||
if len(text) == 0 {
|
if len(text) == 0 {
|
||||||
return nil
|
return nil
|
||||||
@ -239,65 +240,65 @@ func ParseMembershipEvent(room *rooms.Room, evt *event.Event) *UIMessage {
|
|||||||
return NewExpandedTextMessage(evt, displayname, text)
|
return NewExpandedTextMessage(evt, displayname, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseAliasEvent(evt *event.Event, displayname string) tstring.TString {
|
//func ParseAliasEvent(evt *muksevt.Event, displayname string) tstring.TString {
|
||||||
var prevAliases []string
|
// var prevAliases []string
|
||||||
if evt.Unsigned.PrevContent != nil {
|
// if evt.Unsigned.PrevContent != nil {
|
||||||
prevAliases = evt.Unsigned.PrevContent.Aliases
|
// prevAliases = evt.Unsigned.PrevContent.Aliases
|
||||||
}
|
// }
|
||||||
aliases := evt.Content.Aliases
|
// aliases := evt.Content.Aliases
|
||||||
var added, removed []tstring.TString
|
// var added, removed []tstring.TString
|
||||||
Outer1:
|
//Outer1:
|
||||||
for _, oldAlias := range prevAliases {
|
// for _, oldAlias := range prevAliases {
|
||||||
for _, newAlias := range aliases {
|
// for _, newAlias := range aliases {
|
||||||
if oldAlias == newAlias {
|
// if oldAlias == newAlias {
|
||||||
continue Outer1
|
// continue Outer1
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
removed = append(removed, tstring.NewStyleTString(oldAlias, tcell.StyleDefault.Foreground(widget.GetHashColor(oldAlias)).Underline(true)))
|
// removed = append(removed, tstring.NewStyleTString(oldAlias, tcell.StyleDefault.Foreground(widget.GetHashColor(oldAlias)).Underline(true)))
|
||||||
}
|
// }
|
||||||
Outer2:
|
//Outer2:
|
||||||
for _, newAlias := range aliases {
|
// for _, newAlias := range aliases {
|
||||||
for _, oldAlias := range prevAliases {
|
// for _, oldAlias := range prevAliases {
|
||||||
if oldAlias == newAlias {
|
// if oldAlias == newAlias {
|
||||||
continue Outer2
|
// continue Outer2
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
added = append(added, tstring.NewStyleTString(newAlias, tcell.StyleDefault.Foreground(widget.GetHashColor(newAlias)).Underline(true)))
|
// added = append(added, tstring.NewStyleTString(newAlias, tcell.StyleDefault.Foreground(widget.GetHashColor(newAlias)).Underline(true)))
|
||||||
}
|
// }
|
||||||
var addedStr, removedStr tstring.TString
|
// var addedStr, removedStr tstring.TString
|
||||||
if len(added) == 1 {
|
// if len(added) == 1 {
|
||||||
addedStr = added[0]
|
// addedStr = added[0]
|
||||||
} else if len(added) > 1 {
|
// } else if len(added) > 1 {
|
||||||
addedStr = tstring.
|
// addedStr = tstring.
|
||||||
Join(added[:len(added)-1], ", ").
|
// Join(added[:len(added)-1], ", ").
|
||||||
Append(" and ").
|
// Append(" and ").
|
||||||
AppendTString(added[len(added)-1])
|
// AppendTString(added[len(added)-1])
|
||||||
}
|
// }
|
||||||
if len(removed) == 1 {
|
// if len(removed) == 1 {
|
||||||
removedStr = removed[0]
|
// removedStr = removed[0]
|
||||||
} else if len(removed) > 1 {
|
// } else if len(removed) > 1 {
|
||||||
removedStr = tstring.
|
// removedStr = tstring.
|
||||||
Join(removed[:len(removed)-1], ", ").
|
// Join(removed[:len(removed)-1], ", ").
|
||||||
Append(" and ").
|
// Append(" and ").
|
||||||
AppendTString(removed[len(removed)-1])
|
// AppendTString(removed[len(removed)-1])
|
||||||
}
|
// }
|
||||||
text := tstring.NewBlankTString()
|
// text := tstring.NewBlankTString()
|
||||||
if len(addedStr) > 0 && len(removedStr) > 0 {
|
// if len(addedStr) > 0 && len(removedStr) > 0 {
|
||||||
text = text.AppendColor(fmt.Sprintf("%s added ", displayname), tcell.ColorGreen).
|
// text = text.AppendColor(fmt.Sprintf("%s added ", displayname), tcell.ColorGreen).
|
||||||
AppendTString(addedStr).
|
// AppendTString(addedStr).
|
||||||
AppendColor(" and removed ", tcell.ColorGreen).
|
// AppendColor(" and removed ", tcell.ColorGreen).
|
||||||
AppendTString(removedStr).
|
// AppendTString(removedStr).
|
||||||
AppendColor(" as addresses for this room.", tcell.ColorGreen)
|
// AppendColor(" as addresses for this room.", tcell.ColorGreen)
|
||||||
} else if len(addedStr) > 0 {
|
// } else if len(addedStr) > 0 {
|
||||||
text = text.AppendColor(fmt.Sprintf("%s added ", displayname), tcell.ColorGreen).
|
// text = text.AppendColor(fmt.Sprintf("%s added ", displayname), tcell.ColorGreen).
|
||||||
AppendTString(addedStr).
|
// AppendTString(addedStr).
|
||||||
AppendColor(" as addresses for this room.", tcell.ColorGreen)
|
// AppendColor(" as addresses for this room.", tcell.ColorGreen)
|
||||||
} else if len(removedStr) > 0 {
|
// } else if len(removedStr) > 0 {
|
||||||
text = text.AppendColor(fmt.Sprintf("%s removed ", displayname), tcell.ColorGreen).
|
// text = text.AppendColor(fmt.Sprintf("%s removed ", displayname), tcell.ColorGreen).
|
||||||
AppendTString(removedStr).
|
// AppendTString(removedStr).
|
||||||
AppendColor(" as addresses for this room.", tcell.ColorGreen)
|
// AppendColor(" as addresses for this room.", tcell.ColorGreen)
|
||||||
} else {
|
// } else {
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
return text
|
// return text
|
||||||
}
|
//}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
package messages
|
package messages
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"maunium.net/go/gomuks/matrix/event"
|
"maunium.net/go/gomuks/matrix/muksevt"
|
||||||
"maunium.net/go/mauview"
|
"maunium.net/go/mauview"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ import (
|
|||||||
|
|
||||||
type RedactedMessage struct{}
|
type RedactedMessage struct{}
|
||||||
|
|
||||||
func NewRedactedMessage(evt *event.Event, displayname string) *UIMessage {
|
func NewRedactedMessage(evt *muksevt.Event, displayname string) *UIMessage {
|
||||||
return newUIMessage(evt, displayname, &RedactedMessage{})
|
return newUIMessage(evt, displayname, &RedactedMessage{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/matrix/event"
|
"maunium.net/go/gomuks/matrix/muksevt"
|
||||||
"maunium.net/go/mauview"
|
"maunium.net/go/mauview"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/config"
|
"maunium.net/go/gomuks/config"
|
||||||
@ -35,7 +35,7 @@ type TextMessage struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewTextMessage creates a new UITextMessage object with the provided values and the default state.
|
// NewTextMessage creates a new UITextMessage object with the provided values and the default state.
|
||||||
func NewTextMessage(evt *event.Event, displayname string, text string) *UIMessage {
|
func NewTextMessage(evt *muksevt.Event, displayname string, text string) *UIMessage {
|
||||||
return newUIMessage(evt, displayname, &TextMessage{
|
return newUIMessage(evt, displayname, &TextMessage{
|
||||||
Text: text,
|
Text: text,
|
||||||
})
|
})
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
|
|
||||||
sync "github.com/sasha-s/go-deadlock"
|
sync "github.com/sasha-s/go-deadlock"
|
||||||
|
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
"maunium.net/go/mauview"
|
"maunium.net/go/mauview"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
|
|
||||||
@ -105,7 +106,7 @@ func NewRoomList(parent *MainView) *RoomList {
|
|||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
func (list *RoomList) Contains(roomID string) bool {
|
func (list *RoomList) Contains(roomID id.RoomID) bool {
|
||||||
list.RLock()
|
list.RLock()
|
||||||
defer list.RUnlock()
|
defer list.RUnlock()
|
||||||
for _, trl := range list.items {
|
for _, trl := range list.items {
|
||||||
|
135
ui/room-view.go
135
ui/room-view.go
@ -26,18 +26,19 @@ import (
|
|||||||
"github.com/kyokomi/emoji"
|
"github.com/kyokomi/emoji"
|
||||||
"github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/debug"
|
|
||||||
"maunium.net/go/gomuks/lib/open"
|
|
||||||
"maunium.net/go/gomuks/matrix/event"
|
|
||||||
|
|
||||||
"maunium.net/go/mauview"
|
"maunium.net/go/mauview"
|
||||||
|
|
||||||
"maunium.net/go/mautrix"
|
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
|
|
||||||
|
"maunium.net/go/mautrix"
|
||||||
|
"maunium.net/go/mautrix/event"
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/config"
|
"maunium.net/go/gomuks/config"
|
||||||
|
"maunium.net/go/gomuks/debug"
|
||||||
"maunium.net/go/gomuks/interface"
|
"maunium.net/go/gomuks/interface"
|
||||||
|
"maunium.net/go/gomuks/lib/open"
|
||||||
"maunium.net/go/gomuks/lib/util"
|
"maunium.net/go/gomuks/lib/util"
|
||||||
|
"maunium.net/go/gomuks/matrix/muksevt"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
"maunium.net/go/gomuks/ui/messages"
|
"maunium.net/go/gomuks/ui/messages"
|
||||||
"maunium.net/go/gomuks/ui/widget"
|
"maunium.net/go/gomuks/ui/widget"
|
||||||
@ -72,9 +73,9 @@ type RoomView struct {
|
|||||||
selectReason SelectReason
|
selectReason SelectReason
|
||||||
selectContent string
|
selectContent string
|
||||||
|
|
||||||
replying *event.Event
|
replying *muksevt.Event
|
||||||
|
|
||||||
editing *event.Event
|
editing *muksevt.Event
|
||||||
editMoveText string
|
editMoveText string
|
||||||
|
|
||||||
completions struct {
|
completions struct {
|
||||||
@ -186,7 +187,7 @@ func (view *RoomView) OnSelect(message *messages.UIMessage) {
|
|||||||
case SelectReply:
|
case SelectReply:
|
||||||
view.replying = message.Event
|
view.replying = message.Event
|
||||||
if len(view.selectContent) > 0 {
|
if len(view.selectContent) > 0 {
|
||||||
go view.SendMessage(mautrix.MsgText, view.selectContent)
|
go view.SendMessage(event.MsgText, view.selectContent)
|
||||||
}
|
}
|
||||||
case SelectReact:
|
case SelectReact:
|
||||||
go view.SendReaction(message.EventID, view.selectContent)
|
go view.SendReaction(message.EventID, view.selectContent)
|
||||||
@ -217,7 +218,7 @@ func (view *RoomView) GetStatus() string {
|
|||||||
buf.WriteString("Editing message - ")
|
buf.WriteString("Editing message - ")
|
||||||
} else if view.replying != nil {
|
} else if view.replying != nil {
|
||||||
buf.WriteString("Replying to ")
|
buf.WriteString("Replying to ")
|
||||||
buf.WriteString(view.replying.Sender)
|
buf.WriteString(string(view.replying.Sender))
|
||||||
buf.WriteString(" - ")
|
buf.WriteString(" - ")
|
||||||
} else if view.selecting {
|
} else if view.selecting {
|
||||||
buf.WriteString("Selecting message to ")
|
buf.WriteString("Selecting message to ")
|
||||||
@ -235,12 +236,19 @@ func (view *RoomView) GetStatus() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(view.typing) == 1 {
|
if len(view.typing) == 1 {
|
||||||
buf.WriteString("Typing: " + view.typing[0])
|
buf.WriteString("Typing: " + string(view.typing[0]))
|
||||||
buf.WriteString(" - ")
|
buf.WriteString(" - ")
|
||||||
} else if len(view.typing) > 1 {
|
} else if len(view.typing) > 1 {
|
||||||
_, _ = fmt.Fprintf(&buf,
|
buf.WriteString("Typing: ")
|
||||||
"Typing: %s and %s - ",
|
for i, userID := range view.typing {
|
||||||
strings.Join(view.typing[:len(view.typing)-1], ", "), view.typing[len(view.typing)-1])
|
if i == len(view.typing)-1 {
|
||||||
|
buf.WriteString(" and ")
|
||||||
|
} else if i > 0 {
|
||||||
|
buf.WriteString(", ")
|
||||||
|
}
|
||||||
|
buf.WriteString(string(userID))
|
||||||
|
}
|
||||||
|
buf.WriteString(" - ")
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.TrimSuffix(buf.String(), " - ")
|
return strings.TrimSuffix(buf.String(), " - ")
|
||||||
@ -384,15 +392,18 @@ func (view *RoomView) SetCompletions(completions []string) {
|
|||||||
|
|
||||||
func (view *RoomView) loadTyping() {
|
func (view *RoomView) loadTyping() {
|
||||||
for index, user := range view.typing {
|
for index, user := range view.typing {
|
||||||
member := view.Room.GetMember(user)
|
member := view.Room.GetMember(id.UserID(user))
|
||||||
if member != nil {
|
if member != nil {
|
||||||
view.typing[index] = member.Displayname
|
view.typing[index] = member.Displayname
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) SetTyping(users []string) {
|
func (view *RoomView) SetTyping(users []id.UserID) {
|
||||||
view.typing = users
|
view.typing = make([]string, len(users))
|
||||||
|
for i, user := range users {
|
||||||
|
view.typing[i] = string(user)
|
||||||
|
}
|
||||||
if view.Room.Loaded() {
|
if view.Room.Loaded() {
|
||||||
view.loadTyping()
|
view.loadTyping()
|
||||||
}
|
}
|
||||||
@ -406,13 +417,13 @@ type completion struct {
|
|||||||
func (view *RoomView) autocompleteUser(existingText string) (completions []completion) {
|
func (view *RoomView) autocompleteUser(existingText string) (completions []completion) {
|
||||||
textWithoutPrefix := strings.TrimPrefix(existingText, "@")
|
textWithoutPrefix := strings.TrimPrefix(existingText, "@")
|
||||||
for userID, user := range view.Room.GetMembers() {
|
for userID, user := range view.Room.GetMembers() {
|
||||||
if user.Displayname == textWithoutPrefix || userID == existingText {
|
if user.Displayname == textWithoutPrefix || string(userID) == existingText {
|
||||||
// Exact match, return that.
|
// Exact match, return that.
|
||||||
return []completion{{user.Displayname, userID}}
|
return []completion{{user.Displayname, string(userID)}}
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(user.Displayname, textWithoutPrefix) || strings.HasPrefix(userID, existingText) {
|
if strings.HasPrefix(user.Displayname, textWithoutPrefix) || strings.HasPrefix(string(userID), existingText) {
|
||||||
completions = append(completions, completion{user.Displayname, userID})
|
completions = append(completions, completion{user.Displayname, string(userID)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -420,13 +431,13 @@ func (view *RoomView) autocompleteUser(existingText string) (completions []compl
|
|||||||
|
|
||||||
func (view *RoomView) autocompleteRoom(existingText string) (completions []completion) {
|
func (view *RoomView) autocompleteRoom(existingText string) (completions []completion) {
|
||||||
for _, room := range view.parent.rooms {
|
for _, room := range view.parent.rooms {
|
||||||
alias := room.Room.GetCanonicalAlias()
|
alias := string(room.Room.GetCanonicalAlias())
|
||||||
if alias == existingText {
|
if alias == existingText {
|
||||||
// Exact match, return that.
|
// Exact match, return that.
|
||||||
return []completion{{alias, room.Room.ID}}
|
return []completion{{alias, string(room.Room.ID)}}
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(alias, existingText) {
|
if strings.HasPrefix(alias, existingText) {
|
||||||
completions = append(completions, completion{alias, room.Room.ID})
|
completions = append(completions, completion{alias, string(room.Room.ID)})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -457,7 +468,7 @@ func (view *RoomView) autocompleteEmoji(word string) (completions []string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) SetEditing(evt *event.Event) {
|
func (view *RoomView) SetEditing(evt *muksevt.Event) {
|
||||||
if evt == nil {
|
if evt == nil {
|
||||||
view.editing = nil
|
view.editing = nil
|
||||||
view.SetInputText(view.editMoveText)
|
view.SetInputText(view.editMoveText)
|
||||||
@ -470,7 +481,7 @@ func (view *RoomView) SetEditing(evt *event.Event) {
|
|||||||
// replying should never be non-nil when SetEditing, but do this just to be safe
|
// replying should never be non-nil when SetEditing, but do this just to be safe
|
||||||
view.replying = nil
|
view.replying = nil
|
||||||
text := view.editing.Content.Body
|
text := view.editing.Content.Body
|
||||||
if view.editing.Content.MsgType == mautrix.MsgEmote {
|
if view.editing.Content.MsgType == event.MsgEmote {
|
||||||
text = "/me " + text
|
text = "/me " + text
|
||||||
}
|
}
|
||||||
view.input.SetText(text)
|
view.input.SetText(text)
|
||||||
@ -479,21 +490,21 @@ func (view *RoomView) SetEditing(evt *event.Event) {
|
|||||||
view.input.SetCursorOffset(-1)
|
view.input.SetCursorOffset(-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
type findFilter func(evt *event.Event) bool
|
type findFilter func(evt *muksevt.Event) bool
|
||||||
|
|
||||||
func (view *RoomView) filterOwnOnly(evt *event.Event) bool {
|
func (view *RoomView) filterOwnOnly(evt *muksevt.Event) bool {
|
||||||
return evt.Sender == view.parent.matrix.Client().UserID && evt.Type == mautrix.EventMessage
|
return evt.Sender == view.parent.matrix.Client().UserID && evt.Type == event.EventMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) filterMediaOnly(evt *event.Event) bool {
|
func (view *RoomView) filterMediaOnly(evt *muksevt.Event) bool {
|
||||||
return evt.Type == mautrix.EventMessage && (
|
return evt.Type == event.EventMessage && (
|
||||||
evt.Content.MsgType == mautrix.MsgFile ||
|
evt.Content.MsgType == event.MsgFile ||
|
||||||
evt.Content.MsgType == mautrix.MsgImage ||
|
evt.Content.MsgType == event.MsgImage ||
|
||||||
evt.Content.MsgType == mautrix.MsgAudio ||
|
evt.Content.MsgType == event.MsgAudio ||
|
||||||
evt.Content.MsgType == mautrix.MsgVideo)
|
evt.Content.MsgType == event.MsgVideo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) findMessage(current *event.Event, forward bool, allow findFilter) *messages.UIMessage {
|
func (view *RoomView) findMessage(current *muksevt.Event, forward bool, allow findFilter) *messages.UIMessage {
|
||||||
currentFound := current == nil
|
currentFound := current == nil
|
||||||
msgs := view.MessageView().messages
|
msgs := view.MessageView().messages
|
||||||
for i := 0; i < len(msgs); i++ {
|
for i := 0; i < len(msgs); i++ {
|
||||||
@ -502,7 +513,7 @@ func (view *RoomView) findMessage(current *event.Event, forward bool, allow find
|
|||||||
index = len(msgs) - i - 1
|
index = len(msgs) - i - 1
|
||||||
}
|
}
|
||||||
evt := msgs[index]
|
evt := msgs[index]
|
||||||
if evt.EventID == "" || evt.EventID == evt.TxnID || evt.IsService {
|
if evt.EventID == "" || string(evt.EventID) == evt.TxnID || evt.IsService {
|
||||||
continue
|
continue
|
||||||
} else if currentFound {
|
} else if currentFound {
|
||||||
if allow == nil || allow(evt.Event) {
|
if allow == nil || allow(evt.Event) {
|
||||||
@ -607,13 +618,13 @@ func (view *RoomView) InputSubmit(text string) {
|
|||||||
} else if cmd := view.parent.cmdProcessor.ParseCommand(view, text); cmd != nil {
|
} else if cmd := view.parent.cmdProcessor.ParseCommand(view, text); cmd != nil {
|
||||||
go view.parent.cmdProcessor.HandleCommand(cmd)
|
go view.parent.cmdProcessor.HandleCommand(cmd)
|
||||||
} else {
|
} else {
|
||||||
go view.SendMessage(mautrix.MsgText, text)
|
go view.SendMessage(event.MsgText, text)
|
||||||
}
|
}
|
||||||
view.editMoveText = ""
|
view.editMoveText = ""
|
||||||
view.SetInputText("")
|
view.SetInputText("")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) Download(url mautrix.ContentURI, filename string, openFile bool) {
|
func (view *RoomView) Download(url id.ContentURI, filename string, openFile bool) {
|
||||||
path, err := view.parent.matrix.DownloadToDisk(url, filename)
|
path, err := view.parent.matrix.DownloadToDisk(url, filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
view.AddServiceMessage(fmt.Sprintf("Failed to download media: %v", err))
|
view.AddServiceMessage(fmt.Sprintf("Failed to download media: %v", err))
|
||||||
@ -627,7 +638,7 @@ func (view *RoomView) Download(url mautrix.ContentURI, filename string, openFile
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) Redact(eventID, reason string) {
|
func (view *RoomView) Redact(eventID id.EventID, reason string) {
|
||||||
defer debug.Recover()
|
defer debug.Recover()
|
||||||
err := view.parent.matrix.Redact(view.Room.ID, eventID, reason)
|
err := view.parent.matrix.Redact(view.Room.ID, eventID, reason)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -642,16 +653,16 @@ func (view *RoomView) Redact(eventID, reason string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) SendReaction(eventID string, reaction string) {
|
func (view *RoomView) SendReaction(eventID id.EventID, reaction string) {
|
||||||
defer debug.Recover()
|
defer debug.Recover()
|
||||||
debug.Print("Reacting to", eventID, "in", view.Room.ID, "with", reaction)
|
debug.Print("Reacting to", eventID, "in", view.Room.ID, "with", reaction)
|
||||||
eventID, err := view.parent.matrix.SendEvent(&event.Event{
|
eventID, err := view.parent.matrix.SendEvent(&muksevt.Event{
|
||||||
Event: &mautrix.Event{
|
Event: &event.Event{
|
||||||
Type: mautrix.EventReaction,
|
Type: event.EventReaction,
|
||||||
RoomID: view.Room.ID,
|
RoomID: view.Room.ID,
|
||||||
Content: mautrix.Content{
|
Content: event.Content{
|
||||||
RelatesTo: &mautrix.RelatesTo{
|
RelatesTo: &event.RelatesTo{
|
||||||
Type: mautrix.RelAnnotation,
|
Type: event.RelAnnotation,
|
||||||
EventID: eventID,
|
EventID: eventID,
|
||||||
Key: reaction,
|
Key: reaction,
|
||||||
},
|
},
|
||||||
@ -670,11 +681,11 @@ func (view *RoomView) SendReaction(eventID string, reaction string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) SendMessage(msgtype mautrix.MessageType, text string) {
|
func (view *RoomView) SendMessage(msgtype event.MessageType, text string) {
|
||||||
view.SendMessageHTML(msgtype, text, "")
|
view.SendMessageHTML(msgtype, text, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) SendMessageHTML(msgtype mautrix.MessageType, text, html string) {
|
func (view *RoomView) SendMessageHTML(msgtype event.MessageType, text, html string) {
|
||||||
defer debug.Recover()
|
defer debug.Recover()
|
||||||
debug.Print("Sending message", msgtype, text, "to", view.Room.ID)
|
debug.Print("Sending message", msgtype, text, "to", view.Room.ID)
|
||||||
if !view.config.Preferences.DisableEmojis {
|
if !view.config.Preferences.DisableEmojis {
|
||||||
@ -683,12 +694,12 @@ func (view *RoomView) SendMessageHTML(msgtype mautrix.MessageType, text, html st
|
|||||||
var rel *ifc.Relation
|
var rel *ifc.Relation
|
||||||
if view.editing != nil {
|
if view.editing != nil {
|
||||||
rel = &ifc.Relation{
|
rel = &ifc.Relation{
|
||||||
Type: mautrix.RelReplace,
|
Type: event.RelReplace,
|
||||||
Event: view.editing,
|
Event: view.editing,
|
||||||
}
|
}
|
||||||
} else if view.replying != nil {
|
} else if view.replying != nil {
|
||||||
rel = &ifc.Relation{
|
rel = &ifc.Relation{
|
||||||
Type: mautrix.RelReference,
|
Type: event.RelReference,
|
||||||
Event: view.replying,
|
Event: view.replying,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -699,7 +710,7 @@ func (view *RoomView) SendMessageHTML(msgtype mautrix.MessageType, text, html st
|
|||||||
view.status.SetText(view.GetStatus())
|
view.status.SetText(view.GetStatus())
|
||||||
eventID, err := view.parent.matrix.SendEvent(evt)
|
eventID, err := view.parent.matrix.SendEvent(evt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg.State = event.StateSendFail
|
msg.State = muksevt.StateSendFail
|
||||||
// Show shorter version if available
|
// Show shorter version if available
|
||||||
if httpErr, ok := err.(mautrix.HTTPError); ok {
|
if httpErr, ok := err.(mautrix.HTTPError); ok {
|
||||||
err = httpErr
|
err = httpErr
|
||||||
@ -712,7 +723,7 @@ func (view *RoomView) SendMessageHTML(msgtype mautrix.MessageType, text, html st
|
|||||||
} else {
|
} else {
|
||||||
debug.Print("Event ID received:", eventID)
|
debug.Print("Event ID received:", eventID)
|
||||||
msg.EventID = eventID
|
msg.EventID = eventID
|
||||||
msg.State = event.StateDefault
|
msg.State = muksevt.StateDefault
|
||||||
view.MessageView().setMessageID(msg)
|
view.MessageView().setMessageID(msg)
|
||||||
view.parent.parent.Render()
|
view.parent.parent.Render()
|
||||||
}
|
}
|
||||||
@ -734,8 +745,8 @@ func (view *RoomView) Update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) UpdateUserList() {
|
func (view *RoomView) UpdateUserList() {
|
||||||
pls := &mautrix.PowerLevels{}
|
pls := &event.PowerLevels{}
|
||||||
if plEvent := view.Room.GetStateEvent(mautrix.StatePowerLevels, ""); plEvent != nil {
|
if plEvent := view.Room.GetStateEvent(event.StatePowerLevels, ""); plEvent != nil {
|
||||||
pls = plEvent.Content.GetPowerLevels()
|
pls = plEvent.Content.GetPowerLevels()
|
||||||
}
|
}
|
||||||
view.userList.Update(view.Room.GetMembers(), pls)
|
view.userList.Update(view.Room.GetMembers(), pls)
|
||||||
@ -746,17 +757,17 @@ func (view *RoomView) AddServiceMessage(text string) {
|
|||||||
view.content.AddMessage(messages.NewServiceMessage(text), AppendMessage)
|
view.content.AddMessage(messages.NewServiceMessage(text), AppendMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) parseEvent(evt *event.Event) *messages.UIMessage {
|
func (view *RoomView) parseEvent(evt *muksevt.Event) *messages.UIMessage {
|
||||||
return messages.ParseEvent(view.parent.matrix, view.parent, view.Room, evt)
|
return messages.ParseEvent(view.parent.matrix, view.parent, view.Room, evt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) AddHistoryEvent(evt *event.Event) {
|
func (view *RoomView) AddHistoryEvent(evt *muksevt.Event) {
|
||||||
if msg := view.parseEvent(evt); msg != nil {
|
if msg := view.parseEvent(evt); msg != nil {
|
||||||
view.content.AddMessage(msg, PrependMessage)
|
view.content.AddMessage(msg, PrependMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) AddEvent(evt *event.Event) ifc.Message {
|
func (view *RoomView) AddEvent(evt *muksevt.Event) ifc.Message {
|
||||||
if msg := view.parseEvent(evt); msg != nil {
|
if msg := view.parseEvent(evt); msg != nil {
|
||||||
view.content.AddMessage(msg, AppendMessage)
|
view.content.AddMessage(msg, AppendMessage)
|
||||||
return msg
|
return msg
|
||||||
@ -764,17 +775,17 @@ func (view *RoomView) AddEvent(evt *event.Event) ifc.Message {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) AddRedaction(redactedEvt *event.Event) {
|
func (view *RoomView) AddRedaction(redactedEvt *muksevt.Event) {
|
||||||
view.AddEvent(redactedEvt)
|
view.AddEvent(redactedEvt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) AddEdit(evt *event.Event) {
|
func (view *RoomView) AddEdit(evt *muksevt.Event) {
|
||||||
if msg := view.parseEvent(evt); msg != nil {
|
if msg := view.parseEvent(evt); msg != nil {
|
||||||
view.content.AddMessage(msg, IgnoreMessage)
|
view.content.AddMessage(msg, IgnoreMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) AddReaction(evt *event.Event, key string) {
|
func (view *RoomView) AddReaction(evt *muksevt.Event, key string) {
|
||||||
msgView := view.MessageView()
|
msgView := view.MessageView()
|
||||||
msg := msgView.getMessageByID(evt.ID)
|
msg := msgView.getMessageByID(evt.ID)
|
||||||
if msg == nil {
|
if msg == nil {
|
||||||
@ -790,7 +801,7 @@ func (view *RoomView) AddReaction(evt *event.Event, key string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) GetEvent(eventID string) ifc.Message {
|
func (view *RoomView) GetEvent(eventID id.EventID) ifc.Message {
|
||||||
message, ok := view.content.messageIDs[eventID]
|
message, ok := view.content.messageIDs[eventID]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
|
@ -19,6 +19,7 @@ package ui
|
|||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
|
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
@ -75,7 +76,7 @@ func (ui *GomuksUI) NewLoginView() mauview.Component {
|
|||||||
|
|
||||||
hs := ui.gmx.Config().HS
|
hs := ui.gmx.Config().HS
|
||||||
view.homeserver.SetPlaceholder("https://example.com").SetText(hs)
|
view.homeserver.SetPlaceholder("https://example.com").SetText(hs)
|
||||||
view.username.SetPlaceholder("@user:example.com").SetText(ui.gmx.Config().UserID)
|
view.username.SetPlaceholder("@user:example.com").SetText(string(ui.gmx.Config().UserID))
|
||||||
view.password.SetPlaceholder("correct horse battery staple").SetMaskCharacter('*')
|
view.password.SetPlaceholder("correct horse battery staple").SetMaskCharacter('*')
|
||||||
|
|
||||||
view.quitButton.SetOnClick(func() { ui.gmx.Stop(true) }).SetBackgroundColor(tcell.ColorDarkCyan)
|
view.quitButton.SetOnClick(func() { ui.gmx.Stop(true) }).SetBackgroundColor(tcell.ColorDarkCyan)
|
||||||
@ -103,7 +104,7 @@ func (ui *GomuksUI) NewLoginView() mauview.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (view *LoginView) resolveWellKnown() {
|
func (view *LoginView) resolveWellKnown() {
|
||||||
_, homeserver, err := mautrix.ParseUserID(view.username.GetText())
|
_, homeserver, err := id.UserID(view.username.GetText()).Parse()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
sync "github.com/sasha-s/go-deadlock"
|
sync "github.com/sasha-s/go-deadlock"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/ui/messages"
|
"maunium.net/go/gomuks/ui/messages"
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
"maunium.net/go/mauview"
|
"maunium.net/go/mauview"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
|
|
||||||
@ -34,9 +35,9 @@ import (
|
|||||||
"maunium.net/go/gomuks/debug"
|
"maunium.net/go/gomuks/debug"
|
||||||
"maunium.net/go/gomuks/interface"
|
"maunium.net/go/gomuks/interface"
|
||||||
"maunium.net/go/gomuks/lib/notification"
|
"maunium.net/go/gomuks/lib/notification"
|
||||||
"maunium.net/go/gomuks/matrix/pushrules"
|
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
"maunium.net/go/gomuks/ui/widget"
|
"maunium.net/go/gomuks/ui/widget"
|
||||||
|
"maunium.net/go/mautrix/pushrules"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MainView struct {
|
type MainView struct {
|
||||||
@ -45,7 +46,7 @@ type MainView struct {
|
|||||||
roomList *RoomList
|
roomList *RoomList
|
||||||
roomView *mauview.Box
|
roomView *mauview.Box
|
||||||
currentRoom *RoomView
|
currentRoom *RoomView
|
||||||
rooms map[string]*RoomView
|
rooms map[id.RoomID]*RoomView
|
||||||
roomsLock sync.RWMutex
|
roomsLock sync.RWMutex
|
||||||
cmdProcessor *CommandProcessor
|
cmdProcessor *CommandProcessor
|
||||||
focused mauview.Focusable
|
focused mauview.Focusable
|
||||||
@ -64,7 +65,7 @@ func (ui *GomuksUI) NewMainView() mauview.Component {
|
|||||||
mainView := &MainView{
|
mainView := &MainView{
|
||||||
flex: mauview.NewFlex().SetDirection(mauview.FlexColumn),
|
flex: mauview.NewFlex().SetDirection(mauview.FlexColumn),
|
||||||
roomView: mauview.NewBox(nil).SetBorder(false),
|
roomView: mauview.NewBox(nil).SetBorder(false),
|
||||||
rooms: make(map[string]*RoomView),
|
rooms: make(map[id.RoomID]*RoomView),
|
||||||
|
|
||||||
matrix: ui.gmx.Matrix(),
|
matrix: ui.gmx.Matrix(),
|
||||||
gmx: ui.gmx,
|
gmx: ui.gmx,
|
||||||
@ -295,7 +296,7 @@ func (view *MainView) addRoomPage(room *rooms.Room) *RoomView {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) GetRoom(roomID string) ifc.RoomView {
|
func (view *MainView) GetRoom(roomID id.RoomID) ifc.RoomView {
|
||||||
room, ok := view.getRoomView(roomID, true)
|
room, ok := view.getRoomView(roomID, true)
|
||||||
if !ok {
|
if !ok {
|
||||||
return view.addRoom(view.matrix.GetOrCreateRoom(roomID))
|
return view.addRoom(view.matrix.GetOrCreateRoom(roomID))
|
||||||
@ -303,7 +304,7 @@ func (view *MainView) GetRoom(roomID string) ifc.RoomView {
|
|||||||
return room
|
return room
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) getRoomView(roomID string, lock bool) (room *RoomView, ok bool) {
|
func (view *MainView) getRoomView(roomID id.RoomID, lock bool) (room *RoomView, ok bool) {
|
||||||
if lock {
|
if lock {
|
||||||
view.roomsLock.RLock()
|
view.roomsLock.RLock()
|
||||||
room, ok = view.rooms[roomID]
|
room, ok = view.rooms[roomID]
|
||||||
@ -357,7 +358,7 @@ func (view *MainView) addRoom(room *rooms.Room) *RoomView {
|
|||||||
func (view *MainView) SetRooms(rooms *rooms.RoomCache) {
|
func (view *MainView) SetRooms(rooms *rooms.RoomCache) {
|
||||||
view.roomList.Clear()
|
view.roomList.Clear()
|
||||||
view.roomsLock.Lock()
|
view.roomsLock.Lock()
|
||||||
view.rooms = make(map[string]*RoomView)
|
view.rooms = make(map[id.RoomID]*RoomView)
|
||||||
for _, room := range rooms.Map {
|
for _, room := range rooms.Map {
|
||||||
if room.HasLeft {
|
if room.HasLeft {
|
||||||
continue
|
continue
|
||||||
@ -383,7 +384,7 @@ func (view *MainView) UpdateTags(room *rooms.Room) {
|
|||||||
view.parent.Render()
|
view.parent.Render()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) SetTyping(roomID string, users []string) {
|
func (view *MainView) SetTyping(roomID id.RoomID, users []id.UserID) {
|
||||||
roomView, ok := view.getRoomView(roomID, true)
|
roomView, ok := view.getRoomView(roomID, true)
|
||||||
if ok {
|
if ok {
|
||||||
roomView.SetTyping(users)
|
roomView.SetTyping(users)
|
||||||
@ -438,7 +439,7 @@ func (view *MainView) NotifyMessage(room *rooms.Room, message ifc.Message, shoul
|
|||||||
message.SetIsHighlight(should.Highlight)
|
message.SetIsHighlight(should.Highlight)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *MainView) LoadHistory(roomID string) {
|
func (view *MainView) LoadHistory(roomID id.RoomID) {
|
||||||
defer debug.Recover()
|
defer debug.Recover()
|
||||||
roomView, ok := view.getRoomView(roomID, true)
|
roomView, ok := view.getRoomView(roomID, true)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -21,6 +21,8 @@ import (
|
|||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
|
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
|
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
var colorNames = []string{
|
var colorNames = []string{
|
||||||
@ -201,8 +203,17 @@ func GetHashColorName(s string) string {
|
|||||||
// GetHashColor gets the tcell Color value for the given string.
|
// GetHashColor gets the tcell Color value for the given string.
|
||||||
//
|
//
|
||||||
// GetHashColor calls GetHashColorName() and gets the Color value from the tcell.ColorNames map.
|
// GetHashColor calls GetHashColorName() and gets the Color value from the tcell.ColorNames map.
|
||||||
func GetHashColor(s string) tcell.Color {
|
func GetHashColor(val interface{}) tcell.Color {
|
||||||
return tcell.ColorNames[GetHashColorName(s)]
|
switch str := val.(type) {
|
||||||
|
case string:
|
||||||
|
return tcell.ColorNames[GetHashColorName(str)]
|
||||||
|
case *string:
|
||||||
|
return tcell.ColorNames[GetHashColorName(*str)]
|
||||||
|
case id.UserID:
|
||||||
|
return tcell.ColorNames[GetHashColorName(string(str))]
|
||||||
|
default:
|
||||||
|
return tcell.ColorNames["red"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddColor adds tview color tags to the given string.
|
// AddColor adds tview color tags to the given string.
|
||||||
|
Loading…
Reference in New Issue
Block a user