From 8a3fbc24ab430443b89dfa45e726ab96ad3ea1ec Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 16 May 2018 20:09:09 +0300 Subject: Handle m.direct and m.receipt events Fixes #12 Fixes #45 --- matrix/matrix.go | 92 ++++++++++++++++++++++++++++++++++++++++++++--- matrix/rooms/room.go | 86 ++++++++++++++++++++++++++++++++++++++------ matrix/rooms/room_test.go | 22 ++++++++---- matrix/sync.go | 4 +-- 4 files changed, 180 insertions(+), 24 deletions(-) (limited to 'matrix') diff --git a/matrix/matrix.go b/matrix/matrix.go index 7fbef5f..92fcabe 100644 --- a/matrix/matrix.go +++ b/matrix/matrix.go @@ -177,7 +177,9 @@ func (c *Container) OnLogin() { c.syncer = NewGomuksSyncer(c.config.Session) c.syncer.OnEventType("m.room.message", c.HandleMessage) c.syncer.OnEventType("m.room.member", c.HandleMembership) + c.syncer.OnEventType("m.receipt", c.HandleReadReceipt) c.syncer.OnEventType("m.typing", c.HandleTyping) + c.syncer.OnEventType("m.direct", c.HandleDirectChatInfo) c.syncer.OnEventType("m.push_rules", c.HandlePushRules) c.syncer.OnEventType("m.tag", c.HandleTag) c.syncer.InitDoneCallback = func() { @@ -228,7 +230,7 @@ func (c *Container) Start() { // HandleMessage is the event handler for the m.room.message timeline event. func (c *Container) HandleMessage(source EventSource, evt *gomatrix.Event) { - if source & EventSourceLeave != 0 { + if source&EventSourceLeave != 0 { return } mainView := c.ui.MainView() @@ -253,6 +255,82 @@ func (c *Container) HandleMessage(source EventSource, evt *gomatrix.Event) { } } +func (c *Container) parseReadReceipt(evt *gomatrix.Event) (largestTimestampEvent string) { + var largestTimestamp int64 + for eventID, rawContent := range evt.Content { + content, ok := rawContent.(map[string]interface{}) + if !ok { + continue + } + + mRead, ok := content["m.read"].(map[string]interface{}) + if !ok { + continue + } + + myInfo, ok := mRead[c.config.Session.UserID].(map[string]interface{}) + if !ok { + continue + } + + ts, ok := myInfo["ts"].(float64) + if int64(ts) > largestTimestamp { + largestTimestamp = int64(ts) + largestTimestampEvent = eventID + } + } + return +} + +func (c *Container) HandleReadReceipt(source EventSource, evt *gomatrix.Event) { + if source&EventSourceLeave != 0 { + return + } + + lastReadEvent := c.parseReadReceipt(evt) + if len(lastReadEvent) == 0 { + return + } + + room := c.GetRoom(evt.RoomID) + room.MarkRead(lastReadEvent) + c.ui.Render() +} + +func (c *Container) parseDirectChatInfo(evt *gomatrix.Event) (map[*rooms.Room]bool){ + directChats := make(map[*rooms.Room]bool) + for _, rawRoomIDList := range evt.Content { + roomIDList, ok := rawRoomIDList.([]interface{}) + if !ok { + continue + } + + for _, rawRoomID := range roomIDList { + roomID, ok := rawRoomID.(string) + if !ok { + continue + } + + room := c.GetRoom(roomID) + if room != nil && !room.HasLeft { + directChats[room] = true + } + } + } + return directChats +} + +func (c *Container) HandleDirectChatInfo(source EventSource, evt *gomatrix.Event) { + directChats := c.parseDirectChatInfo(evt) + for _, room := range c.config.Session.Rooms { + shouldBeDirect := directChats[room] + if shouldBeDirect != room.IsDirect { + room.IsDirect = shouldBeDirect + c.ui.MainView().UpdateTags(room) + } + } +} + // HandlePushRules is the event handler for the m.push_rules account data event. func (c *Container) HandlePushRules(source EventSource, evt *gomatrix.Event) { debug.Print("Received updated push rules") @@ -285,7 +363,8 @@ func (c *Container) HandleTag(source EventSource, evt *gomatrix.Event) { } mainView := c.ui.MainView() - mainView.UpdateTags(room, newTags) + room.RawTags = newTags + mainView.UpdateTags(room) } func (c *Container) processOwnMembershipChange(evt *gomatrix.Event) { @@ -314,8 +393,8 @@ func (c *Container) processOwnMembershipChange(evt *gomatrix.Event) { // HandleMembership is the event handler for the m.room.member state event. func (c *Container) HandleMembership(source EventSource, evt *gomatrix.Event) { - isLeave := source & EventSourceLeave != 0 - isTimeline := source & EventSourceTimeline != 0 + isLeave := source&EventSourceLeave != 0 + isTimeline := source&EventSourceTimeline != 0 isNonTimelineLeave := isLeave && !isTimeline if !c.config.Session.InitialSyncDone && isNonTimelineLeave { return @@ -356,6 +435,11 @@ func (c *Container) HandleTyping(source EventSource, evt *gomatrix.Event) { c.ui.MainView().SetTyping(evt.RoomID, strUsers) } +func (c *Container) MarkRead(roomID, eventID string) { + urlPath := c.client.BuildURL("rooms", roomID, "receipt", "m.read", eventID) + c.client.MakeRequest("POST", urlPath, struct{}{}, nil) +} + // SendMessage sends a message with the given text to the given room. func (c *Container) SendMessage(roomID, msgtype, text string) (string, error) { defer debug.Recover() diff --git a/matrix/rooms/room.go b/matrix/rooms/room.go index 40303be..17bf21b 100644 --- a/matrix/rooms/room.go +++ b/matrix/rooms/room.go @@ -43,6 +43,12 @@ type RoomTag struct { Order string } +type UnreadMessage struct { + EventID string + Counted bool + Highlight bool +} + // Room represents a single Matrix room. type Room struct { *gomatrix.Room @@ -57,13 +63,11 @@ type Room struct { SessionUserID string // The number of unread messages that were notified about. - UnreadMessages int - // Whether or not any of the unread messages were highlights. - Highlighted bool - // Whether or not the room contains any new messages. - // This can be true even when UnreadMessages is zero if there's - // a notificationless message like bot notices. - HasNewMessages bool + UnreadMessages []UnreadMessage + unreadCountCache *int + highlightCache *bool + // Whether or not this room is marked as a direct chat. + IsDirect bool // List of tags given to this room RawTags []RoomTag @@ -110,14 +114,74 @@ func (room *Room) UnlockHistory() { } // MarkRead clears the new message statuses on this room. -func (room *Room) MarkRead() { - room.UnreadMessages = 0 - room.Highlighted = false - room.HasNewMessages = false +func (room *Room) MarkRead(eventID string) { + readToIndex := -1 + for index, unreadMessage := range room.UnreadMessages { + if unreadMessage.EventID == eventID { + readToIndex = index + } + } + if readToIndex >= 0 { + room.UnreadMessages = room.UnreadMessages[readToIndex+1:] + room.highlightCache = nil + room.unreadCountCache = nil + } +} + +func (room *Room) UnreadCount() int { + if room.unreadCountCache == nil { + room.unreadCountCache = new(int) + for _, unreadMessage := range room.UnreadMessages { + if unreadMessage.Counted { + *room.unreadCountCache++ + } + } + } + return *room.unreadCountCache +} + +func (room *Room) Highlighted() bool { + if room.highlightCache == nil { + room.highlightCache = new(bool) + for _, unreadMessage := range room.UnreadMessages { + if unreadMessage.Highlight { + *room.highlightCache = true + break + } + } + } + return *room.highlightCache +} + +func (room *Room) HasNewMessages() bool { + return len(room.UnreadMessages) > 0 +} + +func (room *Room) AddUnread(eventID string, counted, highlight bool) { + room.UnreadMessages = append(room.UnreadMessages, UnreadMessage{ + EventID: eventID, + Counted: counted, + Highlight: highlight, + }) + if counted { + if room.unreadCountCache == nil { + room.unreadCountCache = new(int) + } + *room.unreadCountCache++ + } + if highlight { + if room.highlightCache == nil { + room.highlightCache = new(bool) + } + *room.highlightCache = true + } } func (room *Room) Tags() []RoomTag { if len(room.RawTags) == 0 { + if room.IsDirect { + return []RoomTag{{"net.maunium.gomuks.fake.direct", "0.5"}} + } return []RoomTag{{"", "0.5"}} } return room.RawTags diff --git a/matrix/rooms/room_test.go b/matrix/rooms/room_test.go index 258e57b..1bd47ff 100644 --- a/matrix/rooms/room_test.go +++ b/matrix/rooms/room_test.go @@ -215,11 +215,19 @@ func TestRoom_GetTitle_Members_GroupChat(t *testing.T) { func TestRoom_MarkRead(t *testing.T) { room := rooms.NewRoom("!test:maunium.net", "@tulir:maunium.net") - room.UnreadMessages = 123 - room.Highlighted = true - room.HasNewMessages = true - room.MarkRead() - assert.Zero(t, room.UnreadMessages) - assert.False(t, room.Highlighted) - assert.False(t, room.HasNewMessages) + + 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("") + assert.Empty(t, room.UnreadMessages) } diff --git a/matrix/sync.go b/matrix/sync.go index 93868c7..330a8c5 100644 --- a/matrix/sync.go +++ b/matrix/sync.go @@ -177,14 +177,14 @@ func (s *GomuksSyncer) GetFilterJSON(userID string) json.RawMessage { Limit: 50, }, Ephemeral: gomatrix.FilterPart{ - Types: []string{"m.typing"}, + Types: []string{"m.typing", "m.receipt"}, }, AccountData: gomatrix.FilterPart{ Types: []string{"m.tag"}, }, }, AccountData: gomatrix.FilterPart{ - Types: []string{"m.push_rules"}, + Types: []string{"m.push_rules", "m.direct"}, }, Presence: gomatrix.FilterPart{ Types: []string{}, -- cgit v1.2.3