Handle m.direct and m.receipt events

Fixes #12
Fixes #45
This commit is contained in:
Tulir Asokan
2018-05-16 20:09:09 +03:00
parent c88801a657
commit 8a3fbc24ab
8 changed files with 229 additions and 92 deletions

View File

@ -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()

View File

@ -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

View File

@ -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)
}

View File

@ -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{},