package roomserver import ( "encoding/json" "github.com/dariuszjaniak/warp/server/internal/matrix/auth" "github.com/dariuszjaniak/warp/server/internal/matrix/event" "github.com/dariuszjaniak/warp/server/internal/storage" ) // The state event types the Client-Server API gives a room its public face // with. Only these are in the spec's "stripped state" and in a public room // directory entry, which is why only these are denormalised. const ( TypeName = "m.room.name" TypeTopic = "m.room.topic" TypeAvatar = "m.room.avatar" TypeCanonicalAlias = "m.room.canonical_alias" TypeGuestAccess = "m.room.guest_access" TypeEncryption = "m.room.encryption" TypeMessage = "m.room.message" TypeHistoryVisibility = event.TypeHistoryVisibility ) // History visibility values, in the order the spec lists them. const ( HistoryVisibilityWorldReadable = "world_readable" HistoryVisibilityShared = "shared" HistoryVisibilityInvited = "invited" HistoryVisibilityJoined = "joined" ) // Guest access values. const ( GuestAccessCanJoin = "can_join" GuestAccessForbidden = "forbidden" ) // affectsRoomProfile reports whether an event of this type changes the // denormalised room listing columns. func affectsRoomProfile(eventType string) bool { switch eventType { case event.TypeCreate, event.TypeJoinRules, TypeName, TypeTopic, TypeAvatar, TypeCanonicalAlias, TypeHistoryVisibility, TypeGuestAccess: return true } return false } // roomProfile reads the denormalised listing columns out of a room's state. // // Every field is taken from the state rather than accumulated, so a profile // recomputed after any one of these events is the whole truth about the // room and not a patch that could drift from it. func roomProfile(st auth.State) storage.RoomProfile { profile := storage.RoomProfile{ JoinRule: st.JoinRule(), // These two have spec defaults for a room that never set them, and // the defaults are what a client would apply, so they are what the // stored row should say. HistoryVisibility: HistoryVisibilityShared, GuestAccess: GuestAccessForbidden, } profile.Name = stringFromContent(st, TypeName, "", "name") profile.Topic = stringFromContent(st, TypeTopic, "", "topic") profile.AvatarURL = stringFromContent(st, TypeAvatar, "", "url") profile.CanonicalAlias = stringFromContent(st, TypeCanonicalAlias, "", "alias") profile.RoomType = stringFromContent(st, event.TypeCreate, "", "type") if v := stringFromContent(st, TypeHistoryVisibility, "", "history_visibility"); v != "" { profile.HistoryVisibility = v } if v := stringFromContent(st, TypeGuestAccess, "", "guest_access"); v != "" { profile.GuestAccess = v } return profile } // stringFromContent reads one string property out of a state event's // content, or returns "" when the event, the property or the type is not // what it should be. // // A malformed value is treated as absent on purpose. This is a projection // for a room listing, not an authorisation input: the right behaviour for a // room whose name is a number is to show it without a name, not to fail // every read of the room. func stringFromContent(st auth.State, eventType, stateKey, property string) string { e := st.Get(eventType, stateKey) if e == nil { return "" } var content map[string]json.RawMessage if err := json.Unmarshal(e.Content(), &content); err != nil { return "" } raw, ok := content[property] if !ok { return "" } var value string if err := json.Unmarshal(raw, &value); err != nil { return "" } return value }