From dd009a9dbcd4334db0d134ca147d1afd4d6e5014 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 20 Mar 2018 21:25:13 +0200 Subject: Finish spec-compliant room naming and add docs for room.go --- config/session.go | 2 +- matrix/room/room.go | 119 ++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 93 insertions(+), 28 deletions(-) diff --git a/config/session.go b/config/session.go index 6de9436..e314f5f 100644 --- a/config/session.go +++ b/config/session.go @@ -96,7 +96,7 @@ func (s *Session) LoadNextBatch(_ string) string { func (s *Session) GetRoom(mxid string) *rooms.Room { room, _ := s.Rooms[mxid] if room == nil { - room = rooms.NewRoom(mxid) + room = rooms.NewRoom(mxid, s.MXID) s.Rooms[room.ID] = room } return room diff --git a/matrix/room/room.go b/matrix/room/room.go index 4b3bda2..a67d363 100644 --- a/matrix/room/room.go +++ b/matrix/room/room.go @@ -17,6 +17,8 @@ package room import ( + "fmt" + "maunium.net/go/gomatrix" ) @@ -24,10 +26,12 @@ import ( type Room struct { *gomatrix.Room - PrevBatch string - memberCache map[string]*Member - nameCache string - topicCache string + PrevBatch string + Owner string + memberCache map[string]*Member + firstMemberCache string + nameCache string + topicCache string } // UpdateState updates the room's current state with the given Event. This will clobber events based @@ -40,8 +44,12 @@ func (room *Room) UpdateState(event *gomatrix.Event) { switch event.Type { case "m.room.member": room.memberCache = nil + room.firstMemberCache = "" + fallthrough case "m.room.name": + fallthrough case "m.room.canonical_alias": + fallthrough case "m.room.alias": room.nameCache = "" case "m.room.topic": @@ -74,44 +82,94 @@ func (room *Room) GetTopic() string { return room.topicCache } -// GetTitle returns the display title of the room. -func (room *Room) GetTitle() string { - if len(room.nameCache) == 0 { - nameEvt := room.GetStateEvent("m.room.name", "") - if nameEvt != nil { - room.nameCache, _ = nameEvt.Content["name"].(string) +// updateNameFromNameEvent updates the room display name to be the name set in the name event. +func (room *Room) updateNameFromNameEvent() { + nameEvt := room.GetStateEvent("m.room.name", "") + if nameEvt != nil { + room.nameCache, _ = nameEvt.Content["name"].(string) + } +} + +// updateNameFromCanonicalAlias updates the room display name to be the canonical alias of the room. +func (room *Room) updateNameFromCanonicalAlias() { + canonicalAliasEvt := room.GetStateEvent("m.room.canonical_alias", "") + if canonicalAliasEvt != nil { + room.nameCache, _ = canonicalAliasEvt.Content["alias"].(string) + } +} + +// updateNameFromAliases updates the room display name to be the first room alias it finds. +// +// Deprecated: the Client-Server API recommends against using aliases as display name. +func (room *Room) updateNameFromAliases() { + // TODO the spec says clients should not use m.room.aliases for room names. + // However, Riot also uses m.room.aliases, so this is here now. + aliasEvents := room.GetStateEvents("m.room.aliases") + for _, event := range aliasEvents { + aliases, _ := event.Content["aliases"].([]interface{}) + if len(aliases) > 0 { + room.nameCache, _ = aliases[0].(string) + break } } +} + +// updateNameFromMembers updates the room display name based on the members in this room. +// +// The room name depends on the number of users: +// Less than two users -> "Empty room" +// Exactly two users -> The display name of the other user. +// More than two users -> The display name of one of the other users, followed +// by "and X others", where X is the number of users +// excluding the local user and the named user. +func (room *Room) updateNameFromMembers() { + members := room.GetMembers() + if len(members) <= 1 { + room.nameCache = "Empty room" + } else if len(members) == 2 { + room.nameCache = members[room.firstMemberCache].DisplayName + } else { + firstMember := members[room.firstMemberCache].DisplayName + room.nameCache = fmt.Sprintf("%s and %d others", firstMember, len(members)-2) + } +} + +// updateNameCache updates the room display name based on the room state in the order +// specified in section 11.2.2.5 of r0.3.0 of the Client-Server API specification. +func (room *Room) updateNameCache() { if len(room.nameCache) == 0 { - canonicalAliasEvt := room.GetStateEvent("m.room.canonical_alias", "") - if canonicalAliasEvt != nil { - room.nameCache, _ = canonicalAliasEvt.Content["alias"].(string) - } + room.updateNameFromNameEvent() } if len(room.nameCache) == 0 { - // TODO the spec says clients should not use m.room.aliases for room names. - // However, Riot also uses m.room.aliases, so this is here now. - aliasEvents := room.GetStateEvents("m.room.aliases") - for _, event := range aliasEvents { - aliases, _ := event.Content["aliases"].([]interface{}) - if len(aliases) > 0 { - room.nameCache, _ = aliases[0].(string) - break - } - } + room.updateNameFromCanonicalAlias() + } + if len(room.nameCache) == 0 { + room.updateNameFromAliases() } if len(room.nameCache) == 0 { - // TODO follow other title rules in spec - room.nameCache = room.ID + room.updateNameFromMembers() } +} + +// GetTitle returns the display name of the room. +// +// The display name is returned from the cache. +// If the cache is empty, it is updated first. +func (room *Room) GetTitle() string { + room.updateNameCache() return room.nameCache } +// createMemberCache caches all member events into a easily processable MXID -> *Member map. func (room *Room) createMemberCache() map[string]*Member { cache := make(map[string]*Member) events := room.GetStateEvents("m.room.member") + room.firstMemberCache = "" if events != nil { for userID, event := range events { + if len(room.firstMemberCache) == 0 && userID != room.Owner { + room.firstMemberCache = userID + } member := eventToRoomMember(userID, event) if member.Membership != "leave" { cache[member.UserID] = member @@ -122,6 +180,10 @@ func (room *Room) createMemberCache() map[string]*Member { return cache } +// GetMembers returns the members in this room. +// +// The members are returned from the cache. +// If the cache is empty, it is updated first. func (room *Room) GetMembers() map[string]*Member { if len(room.memberCache) == 0 { room.createMemberCache() @@ -129,6 +191,8 @@ func (room *Room) GetMembers() map[string]*Member { return room.memberCache } +// GetMember returns the member with the given MXID. +// If the member doesn't exist, nil is returned. func (room *Room) GetMember(userID string) *Member { if len(room.memberCache) == 0 { room.createMemberCache() @@ -138,8 +202,9 @@ func (room *Room) GetMember(userID string) *Member { } // NewRoom creates a new Room with the given ID -func NewRoom(roomID string) *Room { +func NewRoom(roomID, owner string) *Room { return &Room{ Room: gomatrix.NewRoom(roomID), + Owner: owner, } } -- cgit v1.2.3