diff --git a/include/ircd/m/room.h b/include/ircd/m/room.h index 87faede11..6ede76349 100644 --- a/include/ircd/m/room.h +++ b/include/ircd/m/room.h @@ -105,6 +105,7 @@ struct ircd::m::room struct members; struct origins; struct head; + struct power; using id = m::id::room; using alias = m::id::room_alias; @@ -373,6 +374,62 @@ struct ircd::m::room::head {} }; +/// Interface to the power levels +/// +/// This interface focuses specifically on making the power levels accessible +/// to developers for common query and manipulation operations. power_levels +/// is a single state event in the room containing integer thresholds for +/// privileges in the room in addition to a list of users mapping to an integer +/// for their level. This interface hides the details of that event by +/// presenting single operations which can appear succinctly in IRCd code. +/// +struct ircd::m::room::power +{ + using closure = std::function; + using closure_bool = std::function; + + static const int64_t default_power_level; + static const int64_t default_event_level; + static const int64_t default_user_level; + + m::room room; + m::room::state state; + + bool view(const std::function &) const; + + public: + // Iterate a collection usually either "events" or "users" as per spec. + bool for_each(const string_view &prop, const closure_bool &) const; + void for_each(const string_view &prop, const closure &) const; + + // Iterates all of the integer levels, excludes the collections. + bool for_each(const closure_bool &) const; + void for_each(const closure &) const; + + bool has_level(const string_view &prop) const; + bool has_collection(const string_view &prop) const; + bool has_event(const string_view &type) const; + bool has_user(const m::id::user &) const; + + size_t count(const string_view &prop) const; + size_t count_collections() const; + size_t count_levels() const; + + // This suite queries with full defaulting logic as per the spec. These + // always return suitable results. + int64_t level(const string_view &prop) const; + int64_t level_event(const string_view &type) const; + int64_t level_user(const m::id::user &) const; + + // all who attain great power and riches make use of either force or fraud" + bool operator()(const m::id::user &, const string_view &prop, const string_view &type = {}) const; + + power(const m::room &room) + :room{room} + ,state{room} + {} +}; + /// Tuple to represent fundamental room state singletons (state_key = "") /// /// This is not a complete representation of room state. Missing from here diff --git a/ircd/m/room.cc b/ircd/m/room.cc index c1d196c91..ffb7007d6 100644 --- a/ircd/m/room.cc +++ b/ircd/m/room.cc @@ -1730,6 +1730,350 @@ const return true; } +// +// room::power +// + +decltype(ircd::m::room::power::default_power_level) +ircd::m::room::power::default_power_level +{ + 50 +}; + +decltype(ircd::m::room::power::default_event_level) +ircd::m::room::power::default_event_level +{ + 0 +}; + +decltype(ircd::m::room::power::default_user_level) +ircd::m::room::power::default_user_level +{ + 0 +}; + +bool +ircd::m::room::power::operator()(const m::user::id &user_id, + const string_view &prop, + const string_view &type) +const +{ + const auto &user_level + { + level_user(user_id) + }; + + const auto &required_level + { + prop == "events"? + level_event(type): + level(prop) + }; + + return user_level >= required_level; +} + +int64_t +ircd::m::room::power::level_user(const m::user::id &user_id) +const try +{ + int64_t ret + { + default_user_level + }; + + view([&user_id, &ret] + (const json::object &content) + { + const json::object &users + { + content.at("users") + }; + + ret = users.at(user_id); + }); + + return ret; +} +catch(const json::error &e) +{ + return default_user_level; +} + +int64_t +ircd::m::room::power::level_event(const string_view &type) +const try +{ + int64_t ret + { + default_event_level + }; + + view([&type, &ret] + (const json::object &content) + { + const json::object &events + { + content.at("events") + }; + + ret = events.at(type); + }); + + return ret; +} +catch(const json::error &e) +{ + return default_event_level; +} + +int64_t +ircd::m::room::power::level(const string_view &prop) +const try +{ + int64_t ret + { + default_power_level + }; + + view([&prop, &ret] + (const json::object &content) + { + ret = content.at(prop); + }); + + return ret; +} +catch(const json::error &e) +{ + return default_power_level; +} + +size_t +ircd::m::room::power::count_levels() +const +{ + size_t ret{0}; + for_each([&ret] + (const string_view &, const int64_t &) + { + ++ret; + }); + + return ret; +} + +size_t +ircd::m::room::power::count_collections() +const +{ + size_t ret{0}; + view([&ret] + (const json::object &content) + { + for(const auto &member : content) + ret += json::type(member.second) == json::OBJECT; + }); + + return ret; +} + +size_t +ircd::m::room::power::count(const string_view &prop) +const +{ + size_t ret{0}; + for_each(prop, [&ret] + (const string_view &, const int64_t &) + { + ++ret; + }); + + return ret; +} + +bool +ircd::m::room::power::has_event(const string_view &type) +const try +{ + bool ret{false}; + view([&type, &ret] + (const json::object &content) + { + const json::object &events + { + content.at("events") + }; + + const string_view &level + { + events.at(type) + }; + + ret = json::type(level) == json::NUMBER; + }); + + return ret; +} +catch(const json::error &) +{ + return false; +} + +bool +ircd::m::room::power::has_user(const m::user::id &user_id) +const try +{ + bool ret{false}; + view([&user_id, &ret] + (const json::object &content) + { + const json::object &users + { + content.at("users") + }; + + const string_view &level + { + users.at(user_id) + }; + + ret = json::type(level) == json::NUMBER; + }); + + return ret; +} +catch(const json::error &) +{ + return false; +} + +bool +ircd::m::room::power::has_collection(const string_view &prop) +const +{ + bool ret{false}; + view([&prop, &ret] + (const json::object &content) + { + const auto &value{content.get(prop)}; + if(value && json::type(value) == json::OBJECT) + ret = true; + }); + + return ret; +} + +bool +ircd::m::room::power::has_level(const string_view &prop) +const +{ + bool ret{false}; + view([&prop, &ret] + (const json::object &content) + { + const auto &value{content.get(prop)}; + if(value && json::type(value) == json::NUMBER) + ret = true; + }); + + return ret; +} + +void +ircd::m::room::power::for_each(const closure &closure) +const +{ + for_each(string_view{}, closure); +} + +bool +ircd::m::room::power::for_each(const closure_bool &closure) +const +{ + return for_each(string_view{}, closure); +} + +void +ircd::m::room::power::for_each(const string_view &prop, + const closure &closure) +const +{ + for_each(prop, closure_bool{[&closure] + (const string_view &key, const int64_t &level) + { + closure(key, level); + return true; + }}); +} + +bool +ircd::m::room::power::for_each(const string_view &prop, + const closure_bool &closure) +const +{ + bool ret{true}; + view([&prop, &closure, &ret] + (const json::object &content) + { + const json::object &collection + { + // This little cmov gimmick sets collection to be the outer object + // itself if no property was given, allowing us to reuse this func + // for all iterations of key -> level mappings. + prop? json::object{content.get(prop)} : content + }; + + if(prop && json::type(string_view{collection}) != json::OBJECT) + return; + + for(auto it(begin(collection)); it != end(collection) && ret; ++it) + { + const auto &member(*it); + if(json::type(member.second) != json::NUMBER) + continue; + + const auto &key + { + unquote(member.first) + }; + + const auto &val + { + lex_cast(member.second) + }; + + ret = closure(key, val); + } + }); + + return ret; +} + +bool +ircd::m::room::power::view(const std::function &closure) +const +{ + static const event::keys keys + { + event::keys::include + { + "content", + } + }; + + const m::event::fetch::opts fopts + { + keys, state.fopts? state.fopts->gopts : db::gopts{} + }; + + return state.get(std::nothrow, "m.room.power_levels", "", [&closure] + (const m::event &event) + { + closure(json::get<"content"_>(event)); + }); +} + // // room::state::tuple //