diff --git a/go.mod b/go.mod index 12b9067..c1a6062 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( gopkg.in/russross/blackfriday.v2 v2.0.1 gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2 gopkg.in/yaml.v2 v2.2.2 - maunium.net/go/mautrix v0.1.0-alpha.3.0.20190616163219-6b4bce05f314 + maunium.net/go/mautrix v0.1.0-alpha.3.0.20190616174145-055ff7b0fa6a maunium.net/go/mauview v0.0.0-20190606152754-de9e0a754a5d maunium.net/go/tcell v0.0.0-20190606152714-9a88fc07b3ed ) diff --git a/go.sum b/go.sum index 492f270..25767fc 100644 --- a/go.sum +++ b/go.sum @@ -98,6 +98,8 @@ maunium.net/go/mautrix v0.1.0-alpha.3.0.20190616140407-62f6f484857e h1:W3NG02SHY maunium.net/go/mautrix v0.1.0-alpha.3.0.20190616140407-62f6f484857e/go.mod h1:O+QWJP3H7BZEzIBSrECKpnpRnEKBwaoWVEu/yZwVwxg= maunium.net/go/mautrix v0.1.0-alpha.3.0.20190616163219-6b4bce05f314 h1:wGlIqs/L+ErBbAHbkpM4AlFi+9+z9Bsle4xD4Gw+y3k= maunium.net/go/mautrix v0.1.0-alpha.3.0.20190616163219-6b4bce05f314/go.mod h1:O+QWJP3H7BZEzIBSrECKpnpRnEKBwaoWVEu/yZwVwxg= +maunium.net/go/mautrix v0.1.0-alpha.3.0.20190616174145-055ff7b0fa6a h1:DenEIDOOumsU8zDzj5ePQOSaKsxjAxW7UESJ2xo1lxM= +maunium.net/go/mautrix v0.1.0-alpha.3.0.20190616174145-055ff7b0fa6a/go.mod h1:O+QWJP3H7BZEzIBSrECKpnpRnEKBwaoWVEu/yZwVwxg= maunium.net/go/mauview v0.0.0-20190606152754-de9e0a754a5d h1:H4wZ4vMVnOh5QFsb4xZtssgpv3DDEkBRzQ8iyEg2fX0= maunium.net/go/mauview v0.0.0-20190606152754-de9e0a754a5d/go.mod h1:GL+akv58wNFzzX4IKLvryKx0F/AcYKHql35DiBzBc/w= maunium.net/go/tcell v0.0.0-20190606152714-9a88fc07b3ed h1:sAcUrUZG2LFWBTkTtLKPQvHPHFM5d6huAhr5ZZuxtbQ= diff --git a/matrix/history.go b/matrix/history.go index 7275c15..af81ed2 100644 --- a/matrix/history.go +++ b/matrix/history.go @@ -21,6 +21,7 @@ import ( "compress/gzip" "encoding/binary" "encoding/gob" + "errors" sync "github.com/sasha-s/go-deadlock" bolt "go.etcd.io/bbolt" @@ -107,6 +108,37 @@ func (hm *HistoryManager) Get(room *rooms.Room, eventID string) (event *mautrix. return } +var EventNotFoundError = errors.New("event not found") + +func (hm *HistoryManager) Update(room *rooms.Room, eventID string, update func(event *mautrix.Event) error) error { + return hm.db.Update(func (tx *bolt.Tx) error { + rid := []byte(room.ID) + eventIDs := tx.Bucket(bucketRoomEventIDs).Bucket(rid) + if eventIDs == nil { + return nil + } + streamIndex := eventIDs.Get([]byte(eventID)) + if streamIndex == nil { + return nil + } + stream := tx.Bucket(bucketRoomStreams).Bucket(rid) + eventData := stream.Get(streamIndex) + if eventData == nil { + return EventNotFoundError + } + + if event, err := unmarshalEvent(eventData); err != nil { + return err + } else if err = update(event); err != nil { + return err + } else if eventData, err = marshalEvent(event); err != nil { + return err + } else { + return stream.Put(streamIndex, eventData) + } + }) +} + func (hm *HistoryManager) Append(room *rooms.Room, events []*mautrix.Event) error { return hm.store(room, events, true) } diff --git a/matrix/matrix.go b/matrix/matrix.go index 05736ce..9178ca0 100644 --- a/matrix/matrix.go +++ b/matrix/matrix.go @@ -209,6 +209,7 @@ func (c *Container) OnLogin() { // Just pass encrypted events as messages, they'll show up with an encryption unsupported message. c.syncer.OnEventType(mautrix.EventEncrypted, c.HandleMessage) c.syncer.OnEventType(mautrix.EventSticker, c.HandleMessage) + c.syncer.OnEventType(mautrix.EventRedaction, c.HandleRedaction) c.syncer.OnEventType(mautrix.StateAliases, c.HandleMessage) c.syncer.OnEventType(mautrix.StateCanonicalAlias, c.HandleMessage) c.syncer.OnEventType(mautrix.StateTopic, c.HandleMessage) @@ -307,6 +308,41 @@ func (c *Container) SendPreferencesToMatrix() { } } +func (c *Container) HandleRedaction(source EventSource, evt *mautrix.Event) { + room := c.GetOrCreateRoom(evt.RoomID) + var redactedEvt *mautrix.Event + err := c.history.Update(room, evt.Redacts, func(redacted *mautrix.Event) error { + redacted.Unsigned.RedactedBy = evt.ID + redacted.Unsigned.RedactedBecause = evt + redactedEvt = redacted + return nil + }) + if err != nil { + debug.Print("Failed to mark", evt.Redacts, "as redacted:", err) + } + + if !room.Loaded() { + return + } + + mainView := c.ui.MainView() + + roomView := mainView.GetRoom(evt.RoomID) + if roomView == nil { + debug.Printf("Failed to handle event %v: No room view found.", evt) + return + } + + // TODO make this less hacky? + message := roomView.ParseEvent(redactedEvt) + if message != nil { + roomView.AddMessage(message) + if c.syncer.FirstSyncDone { + c.ui.Render() + } + } +} + // HandleMessage is the event handler for the m.room.message timeline event. func (c *Container) HandleMessage(source EventSource, evt *mautrix.Event) { room := c.GetOrCreateRoom(evt.RoomID) diff --git a/ui/messages/parser.go b/ui/messages/parser.go index 29f078c..0b1965e 100644 --- a/ui/messages/parser.go +++ b/ui/messages/parser.go @@ -69,6 +69,9 @@ func directParseEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix if member != nil { displayname = member.Displayname } + if evt.Unsigned.RedactedBecause != nil || evt.Type == mautrix.EventRedaction { + return NewRedactedMessage(evt, displayname) + } switch evt.Type { case mautrix.EventSticker: evt.Content.MsgType = mautrix.MsgImage diff --git a/ui/messages/redactedmessage.go b/ui/messages/redactedmessage.go new file mode 100644 index 0000000..b19ccad --- /dev/null +++ b/ui/messages/redactedmessage.go @@ -0,0 +1,68 @@ +// 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 . + +package messages + +import ( + ifc "maunium.net/go/gomuks/interface" + "maunium.net/go/mautrix" + "maunium.net/go/mauview" + "maunium.net/go/tcell" + + "maunium.net/go/gomuks/config" +) + +type RedactedMessage struct{} + +func NewRedactedMessage(event *mautrix.Event, displayname string) *UIMessage { + return newUIMessage(event, displayname, &RedactedMessage{}) +} + +func (msg *RedactedMessage) Clone() MessageRenderer { + return &RedactedMessage{} +} + +func (msg *RedactedMessage) NotificationContent() string { + return "" +} + +func (msg *RedactedMessage) PlainText() string { + return "[redacted]" +} + +func (msg *RedactedMessage) String() string { + return "&messages.RedactedMessage{}" +} + +func (msg *RedactedMessage) CalculateBuffer(prefs config.UserPreferences, width int, uiMsg *UIMessage) { +} + +func (msg *RedactedMessage) Height() int { + return 1 +} + +const RedactionChar = '█' +const RedactionMaxWidth = 40 +var RedactionStyle = tcell.StyleDefault.Foreground(tcell.NewRGBColor(50, 0, 0)) + +func (msg *RedactedMessage) Draw(screen mauview.Screen) { + w, _ := screen.Size() + for x := 0; x < w && x < RedactionMaxWidth; x++ { + screen.SetContent(x, 0, RedactionChar, nil, RedactionStyle) + } +} + +func (msg *RedactedMessage) RegisterMatrix(matrix ifc.MatrixContainer) {}