From e3bc05884565613c3b0ef3425d966fada8446cc9 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 20 Feb 2026 01:32:37 +0100 Subject: [PATCH] Refactor v12 support to use new user_level helper from mtxclient --- CMakeLists.txt | 2 +- im.nheko.Nheko.yaml | 4 +- resources/qml/TimelineSectionHeader.qml | 3 +- .../qml/components/PowerlevelIndicator.qml | 11 +-- resources/qml/dialogs/PowerLevelEditor.qml | 26 ++++-- resources/qml/dialogs/RoomMembers.qml | 1 - src/Cache.cpp | 89 ++++--------------- src/Cache.h | 4 - src/Cache_p.h | 4 - src/MemberList.cpp | 29 +----- src/MemberList.h | 2 +- src/PowerlevelsEditModels.cpp | 78 +++++++++++----- src/PowerlevelsEditModels.h | 30 +++++-- src/Utils.cpp | 47 ++++++++-- src/timeline/Permissions.cpp | 39 ++++---- src/timeline/Permissions.h | 10 ++- src/timeline/TimelineModel.cpp | 35 ++++---- src/timeline/TimelineModel.h | 1 - 18 files changed, 208 insertions(+), 207 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b76c488..0976231b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -622,7 +622,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG d6f10427d1c5e5b1a45f426274f8d2e8dd0b64be + GIT_TAG 873911e352a0845dfb178f77b1ddea796a5d3455 ) set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") diff --git a/im.nheko.Nheko.yaml b/im.nheko.Nheko.yaml index b7bc6dee..76f15bb9 100644 --- a/im.nheko.Nheko.yaml +++ b/im.nheko.Nheko.yaml @@ -213,8 +213,8 @@ modules: - -DBUILD_SHARED_LIBS=OFF buildsystem: cmake-ninja sources: - - commit: 15b43844f4ec27faa5f2ec92c4ded313206763aa - tag: v0.10.1 + - commit: 873911e352a0845dfb178f77b1ddea796a5d3455 + #tag: v0.10.1 type: git url: https://github.com/Nheko-Reborn/mtxclient.git - name: nheko diff --git a/resources/qml/TimelineSectionHeader.qml b/resources/qml/TimelineSectionHeader.qml index f504c463..2a95807c 100644 --- a/resources/qml/TimelineSectionHeader.qml +++ b/resources/qml/TimelineSectionHeader.qml @@ -89,7 +89,6 @@ Column { anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter - isV12Creator: room.isV12Creator(userId) powerlevel: userPowerlevel height: fontMetrics.ascent width: height @@ -98,7 +97,7 @@ Column { sourceSize.height: height permissions: room ? room.permissions : null - visible: isAdmin || isModerator || isV12Creator + visible: isAdmin || isModerator // implicitly includes creators as well } ToolTip.delay: Nheko.tooltipDelay diff --git a/resources/qml/components/PowerlevelIndicator.qml b/resources/qml/components/PowerlevelIndicator.qml index 07453828..00942aec 100644 --- a/resources/qml/components/PowerlevelIndicator.qml +++ b/resources/qml/components/PowerlevelIndicator.qml @@ -7,10 +7,10 @@ import QtQuick.Controls import im.nheko Image { - required property int powerlevel + required property var powerlevel required property var permissions - required property bool isV12Creator + readonly property bool isV12Creator: permissions ? permissions.creatorLevel() == powerlevel : false readonly property bool isAdmin: permissions ? permissions.changeLevel(MtxEvent.PowerLevels) <= powerlevel : false readonly property bool isModerator: permissions ? permissions.redactLevel() <= powerlevel : false readonly property bool isDefault: permissions ? permissions.defaultLevel() <= powerlevel : false @@ -27,14 +27,15 @@ Image { source: sourceUrl + (ma.hovered ? palette.highlight : palette.buttonText) ToolTip.visible: ma.hovered ToolTip.text: { + let pl = powerlevel.toLocaleString(Qt.locale(), "f", 0); if (isV12Creator) return qsTr("Creator"); else if (isAdmin) - return qsTr("Administrator: %1").arg(powerlevel); + return qsTr("Administrator (%1)").arg(pl) else if (isModerator) - return qsTr("Moderator: %1").arg(powerlevel); + return qsTr("Moderator: %1").arg(pl); else - return qsTr("User: %1").arg(powerlevel); + return qsTr("User: %1").arg(pl); } HoverHandler { diff --git a/resources/qml/dialogs/PowerLevelEditor.qml b/resources/qml/dialogs/PowerLevelEditor.qml index 17b19c25..694a259e 100644 --- a/resources/qml/dialogs/PowerLevelEditor.qml +++ b/resources/qml/dialogs/PowerLevelEditor.qml @@ -94,14 +94,17 @@ ApplicationWindow { Text { visible: !model.isType; text: { + let pl = model.powerlevel.toLocaleString(Qt.locale(), "f", 0); + if (editingModel.creatorLevel == model.powerlevel) + return qsTr("Creator") if (editingModel.adminLevel == model.powerlevel) - return qsTr("Administrator (%1)").arg(model.powerlevel) + return qsTr("Administrator (%1)").arg(pl) else if (editingModel.moderatorLevel == model.powerlevel) - return qsTr("Moderator (%1)").arg(model.powerlevel) + return qsTr("Moderator (%1)").arg(pl) else if (editingModel.defaultUserLevel == model.powerlevel) - return qsTr("User (%1)").arg(model.powerlevel) + return qsTr("User (%1)").arg(pl) else - return qsTr("Custom (%1)").arg(model.powerlevel) + return qsTr("Custom (%1)").arg(pl) } color: palette.text } @@ -138,7 +141,7 @@ ApplicationWindow { color: palette.text - Keys.onPressed: { + Keys.onPressed: event => { if (typeEntry.text.includes('.') && event.matches(StandardKey.InsertParagraphSeparator)) { editingModel.types.add(typeEntry.index, typeEntry.text) typeEntry.visible = false; @@ -334,12 +337,17 @@ ApplicationWindow { Text { visible: !model.isUser; text: { + let pl = model.powerlevel.toLocaleString(Qt.locale(), "f", 0); + if (editingModel.creatorLevel == model.powerlevel) + return qsTr("Creator") if (editingModel.adminLevel == model.powerlevel) - return qsTr("Administrator (%1)").arg(model.powerlevel) + return qsTr("Administrator (%1)").arg(pl) else if (editingModel.moderatorLevel == model.powerlevel) - return qsTr("Moderator (%1)").arg(model.powerlevel) + return qsTr("Moderator (%1)").arg(pl) + else if (editingModel.defaultUserLevel == model.powerlevel) + return qsTr("User (%1)").arg(pl) else - return qsTr("Custom (%1)").arg(model.powerlevel) + return qsTr("Custom (%1)").arg(pl) } color: palette.text } @@ -349,7 +357,7 @@ ApplicationWindow { Layout.alignment: Qt.AlignRight Layout.rightMargin: 2 image: model.isUser ? ":/icons/icons/ui/dismiss.svg" : ":/icons/icons/ui/add-square-button.svg" - visible: !model.isUser || model.removeable + visible: (!model.isUser || model.removeable) && model.powerlevel != editingModel.creatorLevel hoverEnabled: true ToolTip.visible: hovered ToolTip.text: model.isUser ? qsTr("Remove user") : qsTr("Add user") diff --git a/resources/qml/dialogs/RoomMembers.qml b/resources/qml/dialogs/RoomMembers.qml index f03fda96..95dc9fc3 100644 --- a/resources/qml/dialogs/RoomMembers.qml +++ b/resources/qml/dialogs/RoomMembers.qml @@ -168,7 +168,6 @@ ApplicationWindow { sourceSize.height: height powerlevel: model.powerlevel permissions: room.permissions - isV12Creator: room.isV12Creator(model.mxid) } EncryptionIndicator { diff --git a/src/Cache.cpp b/src/Cache.cpp index ca8644ba..ee981abc 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -4621,12 +4621,14 @@ Cache::updateSpaces(lmdb::txn &txn, event.state_key.at(0) == '!') { const std::string &space = event.state_key; + auto create = getStateEvent(txn, space) + .value_or(mtx::events::StateEvent{}); auto pls = getStateEvent(txn, space); if (!pls) continue; - if (pls->content.user_level(event.sender) >= + if (pls->content.user_level(event.sender, create) >= pls->content.state_level(space_event_type)) { db->spacesChildren.put(txn, space, room); db->spacesParents.put(txn, room, space); @@ -4635,7 +4637,7 @@ Cache::updateSpaces(lmdb::txn &txn, room, space, event.sender, - pls->content.user_level(event.sender), + pls->content.user_level(event.sender, create), pls->content.state_level(space_event_type)); } } @@ -4851,51 +4853,6 @@ Cache::getAccountData(lmdb::txn &txn, mtx::events::EventType type, const std::st return std::nullopt; } -bool -Cache::isV12Creator(const std::string &room_id, const std::string &user_id) -{ - auto txn = ro_txn(this->db->env_); - auto state = this->getStatesDb(txn, room_id); - - return this->isV12Creator(txn, state, user_id); -} - -bool -Cache::isV12Creator(lmdb::txn &txn, lmdb::dbi &state, const std::string &user_id) -{ - using namespace mtx::events; - using namespace mtx::events::state; - - bool ok; - const int room_version = this->getRoomVersion(txn, state).toInt(&ok); - if (!ok || room_version < 12) { - return false; - } - - std::string_view create_event; - if (state.get(txn, to_string(EventType::RoomCreate), create_event)) { - try { - const StateEvent evt = - nlohmann::json::parse(create_event).get>(); - if (evt.sender == user_id) { - return true; - } - - const std::optional> &additional_creators = - evt.content.additional_creators; - if (additional_creators && - std::find(additional_creators->begin(), additional_creators->end(), user_id) != - additional_creators->end()) { - return true; - } - } catch (...) { - return false; - } - } - - return false; -} - bool Cache::hasEnoughPowerLevel(const std::vector &eventTypes, const std::string &room_id, @@ -4908,30 +4865,22 @@ Cache::hasEnoughPowerLevel(const std::vector &eventTypes try { auto db_ = getStatesDb(txn, room_id); - if (this->isV12Creator(txn, db_, user_id)) { - return true; - } - int64_t min_event_level = std::numeric_limits::max(); int64_t user_level = std::numeric_limits::min(); - std::string_view event; - bool res = db_.get(txn, to_string(EventType::RoomPowerLevels), event); + try { + StateEvent create = getStateEvent(txn, room_id) + .value_or(StateEvent{}); + StateEvent pls = + getStateEvent(txn, room_id) + .value_or(StateEvent{}); - if (res) { - try { - StateEvent msg = - nlohmann::json::parse(std::string_view(event.data(), event.size())) - .get>(); + user_level = pls.content.user_level(user_id, create); - user_level = msg.content.user_level(user_id); - - for (const auto &ty : eventTypes) - min_event_level = - std::min(min_event_level, msg.content.state_level(to_string(ty))); - } catch (const nlohmann::json::exception &e) { - nhlog::db()->warn("failed to parse m.room.power_levels event: {}", e.what()); - } + for (const auto &ty : eventTypes) + min_event_level = std::min(min_event_level, pls.content.state_level(to_string(ty))); + } catch (const nlohmann::json::exception &e) { + nhlog::db()->warn("failed to parse m.room.power_levels event: {}", e.what()); } return user_level >= min_event_level; @@ -6178,13 +6127,6 @@ roomMembers(const std::string &room_id) return instance_->roomMembers(room_id); } -//! Check if the given user is a room creator and that gives them an infinite PL. -bool -isV12Creator(const std::string &room_id, const std::string &user_id) -{ - return instance_->isV12Creator(room_id, user_id); -} - //! Check if the given user has power level greater than //! lowest power level of the given events. bool @@ -6466,6 +6408,7 @@ NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::JoinRules) NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::Name) NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::PinnedEvents) NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::PowerLevels) +NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::Create) NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::ServerAcl) NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::space::Child) NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::space::Parent) diff --git a/src/Cache.h b/src/Cache.h index c6118c78..7c79881c 100644 --- a/src/Cache.h +++ b/src/Cache.h @@ -123,10 +123,6 @@ runMigrations(); std::vector roomMembers(const std::string &room_id); -//! Check if the given user is a room creator and that gives them an infinite PL. -bool -isV12Creator(const std::string &room_id, const std::string &user_id); - //! Check if the given user has power level greater than than //! lowest power level of the given events. bool diff --git a/src/Cache_p.h b/src/Cache_p.h index ee7913d5..89fdd843 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -145,10 +145,6 @@ public: //! Retrieve all the user ids from a room. std::vector roomMembers(const std::string &room_id); - //! Check if the given user is a room creator and that gives them an infinite PL. - bool isV12Creator(const std::string &room_id, const std::string &user_id); - bool isV12Creator(lmdb::txn &txn, lmdb::dbi &state, const std::string &user_id); - //! Check if the given user has power leve greater than than //! lowest power level of the given events. bool hasEnoughPowerLevel(const std::vector &eventTypes, diff --git a/src/MemberList.cpp b/src/MemberList.cpp index bc493165..10487974 100644 --- a/src/MemberList.cpp +++ b/src/MemberList.cpp @@ -19,6 +19,9 @@ MemberListBackend::MemberListBackend(const QString &room_id, QObject *parent) ->getStateEvent(room_id_.toStdString()) .value_or(mtx::events::StateEvent{}) .content} + , create_{cache::client() + ->getStateEvent(room_id_.toStdString()) + .value_or(mtx::events::StateEvent{})} { try { info_ = cache::singleRoomInfo(room_id_.toStdString()); @@ -92,7 +95,7 @@ MemberListBackend::data(const QModelIndex &index, int role) const } case Powerlevel: return static_cast( - powerLevels_.user_level(m_memberList[index.row()].first.user_id.toStdString())); + powerLevels_.user_level(m_memberList[index.row()].first.user_id.toStdString(), create_)); default: return {}; } @@ -172,28 +175,4 @@ MemberList::filterAcceptsRow(int source_row, const QModelIndex &) const Qt::CaseInsensitive); } -bool -MemberList::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const -{ - if (this->sortRole() != MemberSortRoles::Powerlevel) { - return QSortFilterProxyModel::lessThan(source_left, source_right); - } - - const QString &left = - this->m_model.data(source_left, MemberListBackend::Roles::Mxid).toString(); - const QString &right = - this->m_model.data(source_right, MemberListBackend::Roles::Mxid).toString(); - - const std::string &room_id = this->roomId().toStdString(); - if (cache::isV12Creator(room_id, left.toStdString())) { - if (!cache::isV12Creator(room_id, right.toStdString())) { - return false; - } - // If both are creators, sort by mxid. - return left < right; - } - - return QSortFilterProxyModel::lessThan(source_left, source_right); -} - #include "moc_MemberList.cpp" diff --git a/src/MemberList.h b/src/MemberList.h index 46d73b53..0ceaad5e 100644 --- a/src/MemberList.h +++ b/src/MemberList.h @@ -73,6 +73,7 @@ private: bool loadingMoreMembers_{false}; mtx::events::state::PowerLevels powerLevels_; + mtx::events::StateEvent create_; friend class MemberList; }; @@ -122,7 +123,6 @@ public slots: protected: bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; - bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override; private: QString filterString; diff --git a/src/PowerlevelsEditModels.cpp b/src/PowerlevelsEditModels.cpp index 2ecbdd53..cf8a6944 100644 --- a/src/PowerlevelsEditModels.cpp +++ b/src/PowerlevelsEditModels.cpp @@ -17,12 +17,15 @@ #include "Logging.h" #include "MatrixClient.h" -PowerlevelsTypeListModel::PowerlevelsTypeListModel(const std::string &rid, - const mtx::events::state::PowerLevels &pl, - QObject *parent) +PowerlevelsTypeListModel::PowerlevelsTypeListModel( + const std::string &rid, + const mtx::events::state::PowerLevels &pl, + const mtx::events::StateEvent &create, + QObject *parent) : QAbstractListModel(parent) , room_id(rid) , powerLevels_(pl) + , create_(create) { std::set seen_levels; for (const auto &[type, level] : powerLevels_.events) { @@ -40,6 +43,9 @@ PowerlevelsTypeListModel::PowerlevelsTypeListModel(const std::string &rid, seen_levels.insert(level); } } + if (create_.content.room_version_creators_with_infinite_power()) { + seen_levels.insert(mtx::events::state::Creator); + } for (const auto &level : { powerLevels_.events_default, @@ -354,12 +360,15 @@ PowerlevelsTypeListModel::moveRows(const QModelIndex &, return true; } -PowerlevelsUserListModel::PowerlevelsUserListModel(const std::string &rid, - const mtx::events::state::PowerLevels &pl, - QObject *parent) +PowerlevelsUserListModel::PowerlevelsUserListModel( + const std::string &rid, + const mtx::events::state::PowerLevels &pl, + const mtx::events::StateEvent &create, + QObject *parent) : QAbstractListModel(parent) , room_id(rid) , powerLevels_(pl) + , create_(create) { std::set seen_levels; for (const auto &[user, level] : powerLevels_.users) { @@ -378,6 +387,16 @@ PowerlevelsUserListModel::PowerlevelsUserListModel(const std::string &rid, } } + if (create_.content.room_version_creators_with_infinite_power()) { + users.push_back(Entry{"", mtx::events::state::Creator}); + seen_levels.insert(mtx::events::state::Creator); + + users.push_back(Entry{create_.sender, mtx::events::state::Creator}); + for (const auto &user : create.content.additional_creators) { + users.push_back(Entry{user, mtx::events::state::Creator}); + } + } + for (const auto &level : { powerLevels_.events_default, powerLevels_.state_default, @@ -408,7 +427,7 @@ PowerlevelsUserListModel::toUsers() const { std::map> m; for (const auto &[key, pl] : std::as_const(users)) - if (key.size() > 0 && key.at(0) == '@') + if (key.size() > 0 && key.at(0) == '@' && pl != mtx::events::state::Creator) m[key] = pl; return m; } @@ -459,7 +478,7 @@ PowerlevelsUserListModel::data(const QModelIndex &index, int role) const case IsUser: return !user.mxid.empty(); case Moveable: - return !user.mxid.empty(); + return !user.mxid.empty() && user.pl != mtx::events::state::Creator; case Removeable: return !user.mxid.empty() && user.mxid.find('.') != std::string::npos; } @@ -554,7 +573,15 @@ PowerlevelsUserListModel::moveRows(const QModelIndex &, if (users.at(sourceRow).mxid.empty()) return false; - auto pl = users.at(destinationChild > 0 ? destinationChild - 1 : 0).pl; + if (users.at(sourceRow).pl == mtx::events::state::Creator) + return false; + if (users.at(destinationChild).pl == mtx::events::state::Creator) + return false; + + auto pl = users.at(destinationChild > 0 ? destinationChild - 1 : 0).pl; + if (pl == mtx::events::state::Creator) + return false; + auto sourceItem = users.takeAt(sourceRow); sourceItem.pl = pl; @@ -577,9 +604,12 @@ PowerlevelEditingModels::PowerlevelEditingModels(QString room_id, QObject *paren ->getStateEvent(room_id.toStdString()) .value_or(mtx::events::StateEvent{}) .content) - , types_(room_id.toStdString(), powerLevels_, this) - , users_(room_id.toStdString(), powerLevels_, this) - , spaces_(room_id.toStdString(), powerLevels_, this) + , create_(cache::client() + ->getStateEvent(room_id.toStdString()) + .value_or(mtx::events::StateEvent{})) + , types_(room_id.toStdString(), powerLevels_, create_, this) + , users_(room_id.toStdString(), powerLevels_, create_, this) + , spaces_(room_id.toStdString(), powerLevels_, create_, this) , room_id_(room_id.toStdString()) { connect(&types_, @@ -678,16 +708,18 @@ samePl(const mtx::events::state::PowerLevels &a, const mtx::events::state::Power b.redact); } -PowerlevelsSpacesListModel::PowerlevelsSpacesListModel(const std::string &room_id_, - const mtx::events::state::PowerLevels &pl, - QObject *parent) +PowerlevelsSpacesListModel::PowerlevelsSpacesListModel( + const std::string &room_id_, + const mtx::events::state::PowerLevels &pl, + const mtx::events::StateEvent &create, + QObject *parent) : QAbstractListModel(parent) , room_id(std::move(room_id_)) , oldPowerLevels_(std::move(pl)) { beginResetModel(); - spaces.push_back(Entry{room_id, oldPowerLevels_, true}); + spaces.push_back(Entry{room_id, oldPowerLevels_, create, true}); std::unordered_set visited; @@ -703,10 +735,16 @@ PowerlevelsSpacesListModel::PowerlevelsSpacesListModel(const std::string &room_i cache::client()->getStateEvent(s, space); if (parent && parent->content.via && !parent->content.via->empty() && parent->content.canonical) { - auto parentPl = cache::client()->getStateEvent(s); + auto childPl = cache::client()->getStateEvent(s); + auto childCreate = + cache::client()->getStateEvent(s).value_or( + mtx::events::StateEvent{}); - spaces.push_back(Entry{ - s, parentPl ? parentPl->content : mtx::events::state::PowerLevels{}, false}); + spaces.push_back( + Entry{s, + childPl ? childPl->content : mtx::events::state::PowerLevels{}, + childCreate, + false}); addChildren(s); } } @@ -813,7 +851,7 @@ PowerlevelsSpacesListModel::data(QModelIndex const &index, int role) const auto entry = spaces.at(row); switch (role) { case Roles::IsEditable: - return entry.pl.user_level(http::client()->user_id().to_string()) >= + return entry.pl.user_level(http::client()->user_id().to_string(), entry.create) >= entry.pl.state_level(to_string(mtx::events::EventType::RoomPowerLevels)); case Roles::IsDifferentFromBase: return !samePl(entry.pl, oldPowerLevels_); diff --git a/src/PowerlevelsEditModels.h b/src/PowerlevelsEditModels.h index 1fe075b7..edb3a821 100644 --- a/src/PowerlevelsEditModels.h +++ b/src/PowerlevelsEditModels.h @@ -29,9 +29,11 @@ public: Removeable, }; - explicit PowerlevelsTypeListModel(const std::string &room_id_, - const mtx::events::state::PowerLevels &pl, - QObject *parent = nullptr); + explicit PowerlevelsTypeListModel( + const std::string &room_id_, + const mtx::events::state::PowerLevels &pl, + const mtx::events::StateEvent &create, + QObject *parent = nullptr); QHash roleNames() const override; int rowCount(const QModelIndex &) const override { return static_cast(types.size()); } @@ -67,6 +69,7 @@ public: std::string room_id; QVector types; mtx::events::state::PowerLevels powerLevels_; + mtx::events::StateEvent create_; }; class PowerlevelsUserListModel final : public QAbstractListModel @@ -88,9 +91,11 @@ public: Removeable, }; - explicit PowerlevelsUserListModel(const std::string &room_id_, - const mtx::events::state::PowerLevels &pl, - QObject *parent = nullptr); + explicit PowerlevelsUserListModel( + const std::string &room_id_, + const mtx::events::state::PowerLevels &pl, + const mtx::events::StateEvent &create, + QObject *parent = nullptr); QHash roleNames() const override; int rowCount(const QModelIndex &) const override { return static_cast(users.size()); } @@ -121,6 +126,7 @@ public: std::string room_id; QVector users; mtx::events::state::PowerLevels powerLevels_; + mtx::events::StateEvent create_; }; class PowerlevelsSpacesListModel final : public QAbstractListModel @@ -147,9 +153,11 @@ public: ApplyPermissions, }; - explicit PowerlevelsSpacesListModel(const std::string &room_id_, - const mtx::events::state::PowerLevels &pl, - QObject *parent = nullptr); + explicit PowerlevelsSpacesListModel( + const std::string &room_id_, + const mtx::events::state::PowerLevels &pl, + const mtx::events::StateEvent &create, + QObject *parent = nullptr); QHash roleNames() const override; int rowCount(const QModelIndex &) const override { return static_cast(spaces.size()); } @@ -183,6 +191,7 @@ public: std::string roomid; mtx::events::state::PowerLevels pl; + mtx::events::StateEvent create; bool apply = false; }; @@ -203,6 +212,7 @@ class PowerlevelEditingModels final : public QObject Q_PROPERTY(PowerlevelsUserListModel *users READ users CONSTANT) Q_PROPERTY(PowerlevelsTypeListModel *types READ types CONSTANT) Q_PROPERTY(PowerlevelsSpacesListModel *spaces READ spaces CONSTANT) + Q_PROPERTY(qlonglong creatorLevel READ creatorLevel CONSTANT) Q_PROPERTY(qlonglong adminLevel READ adminLevel NOTIFY adminLevelChanged) Q_PROPERTY(qlonglong moderatorLevel READ moderatorLevel NOTIFY moderatorLevelChanged) Q_PROPERTY(qlonglong defaultUserLevel READ defaultUserLevel NOTIFY defaultUserLevelChanged) @@ -222,6 +232,7 @@ public: PowerlevelsUserListModel *users() { return &users_; } PowerlevelsTypeListModel *types() { return &types_; } PowerlevelsSpacesListModel *spaces() { return &spaces_; } + qlonglong creatorLevel() const { return mtx::events::state::Creator; } qlonglong adminLevel() const { return powerLevels_.state_level(to_string(mtx::events::EventType::RoomPowerLevels)); @@ -235,6 +246,7 @@ public: Q_INVOKABLE void addRole(int pl); mtx::events::state::PowerLevels powerLevels_; + mtx::events::StateEvent create_; PowerlevelsTypeListModel types_; PowerlevelsUserListModel users_; PowerlevelsSpacesListModel spaces_; diff --git a/src/Utils.cpp b/src/Utils.cpp index 2bec9d36..4556b680 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -1453,6 +1453,9 @@ utils::roomVias(const std::string &roomid) auto powerlevels = cache::client()->getStateEvent(roomid).value_or( mtx::events::StateEvent{}); + auto create = + cache::client()->getStateEvent(roomid).value_or( + mtx::events::StateEvent{}); auto acls = cache::client()->getStateEvent(roomid); std::vector allowedServers; @@ -1501,6 +1504,19 @@ utils::roomVias(const std::string &roomid) std::set users_with_high_pl_in_room; // we should pick PL > 50, but imo that is broken, so we just pick users who have admins // perm + if (create.content.room_version_creators_with_infinite_power()) { + { + auto user = create.sender; + auto host = mtx::identifiers::parse(user).hostname(); + if (isHostAllowed(host)) + users_with_high_pl.insert(user); + } + for (const auto &user : create.content.additional_creators) { + auto host = mtx::identifiers::parse(user).hostname(); + if (isHostAllowed(host)) + users_with_high_pl.insert(user); + } + } for (const auto &user : powerlevels.content.users) { if (user.second >= powerlevels.content.events_default && user.second >= powerlevels.content.state_default) { @@ -1525,12 +1541,13 @@ utils::roomVias(const std::string &roomid) }); // add the highest powerlevel user - auto max_pl_user = std::max_element( - users_with_high_pl_in_room.begin(), - users_with_high_pl_in_room.end(), - [&pl_content = powerlevels.content](const std::string &a, const std::string &b) { - return pl_content.user_level(a) < pl_content.user_level(b); - }); + auto max_pl_user = std::max_element(users_with_high_pl_in_room.begin(), + users_with_high_pl_in_room.end(), + [&pl_content = powerlevels.content, &create]( + const std::string &a, const std::string &b) { + return pl_content.user_level(a, create) < + pl_content.user_level(b, create); + }); if (max_pl_user != users_with_high_pl_in_room.end()) { auto host = mtx::identifiers::parse(*max_pl_user).hostname(); @@ -1705,11 +1722,15 @@ utils::updateSpaceVias() auto spaceid = roomid.toStdString(); + auto create = cache::client()->getStateEvent(spaceid).value_or( + mtx::events::StateEvent{}); + if (auto pl = cache::client() ->getStateEvent(spaceid) .value_or(mtx::events::StateEvent{}) .content; - pl.user_level(us) < pl.state_level(to_string(mtx::events::EventType::SpaceChild))) + pl.user_level(us, create) < + pl.state_level(to_string(mtx::events::EventType::SpaceChild))) continue; auto children = cache::client()->getChildRoomIds(spaceid); @@ -1748,12 +1769,16 @@ utils::updateSpaceVias() parent->origin_server_ts < weekAgo && // ignore unset spaces (parent->content.via && !parent->content.via->empty())) { + auto childCreate = + cache::client()->getStateEvent(spaceid).value_or( + mtx::events::StateEvent{}); + if (auto pl = cache::client() ->getStateEvent(childid) .value_or(mtx::events::StateEvent{}) .content; - pl.user_level(us) < + pl.user_level(us, childCreate) < pl.state_level(to_string(mtx::events::EventType::SpaceParent))) continue; @@ -2041,11 +2066,15 @@ utils::removeExpiredEvents() if (!asus->globalExpiry && !getExpEv(roomid)) continue; + auto create = cache::client()->getStateEvent(roomid).value_or( + mtx::events::StateEvent{}); + if (auto pl = cache::client() ->getStateEvent(roomid) .value_or(mtx::events::StateEvent{}) .content; - pl.user_level(us) < pl.event_level(to_string(mtx::events::EventType::RoomRedaction))) { + pl.user_level(us, create) < + pl.event_level(to_string(mtx::events::EventType::RoomRedaction))) { nhlog::net()->warn("Can't react events in {}, not running expiration.", roomid); continue; } diff --git a/src/timeline/Permissions.cpp b/src/timeline/Permissions.cpp index 569bf2bc..5e8bd169 100644 --- a/src/timeline/Permissions.cpp +++ b/src/timeline/Permissions.cpp @@ -4,6 +4,8 @@ #include "Permissions.h" +#include + #include "Cache_p.h" #include "MatrixClient.h" #include "TimelineModel.h" @@ -22,50 +24,53 @@ Permissions::invalidate() ->getStateEvent(roomId_.toStdString()) .value_or(mtx::events::StateEvent{}) .content; + create = cache::client() + ->getStateEvent(roomId_.toStdString()) + .value_or(mtx::events::StateEvent{}); } bool Permissions::canInvite() { - const bool plCheck = pl.user_level(http::client()->user_id().to_string()) >= pl.invite; - return plCheck || this->isV12Creator(); + const bool plCheck = pl.user_level(http::client()->user_id().to_string(), create) >= pl.invite; + return plCheck; } bool Permissions::canBan() { - const bool plCheck = pl.user_level(http::client()->user_id().to_string()) >= pl.ban; - return plCheck || this->isV12Creator(); + const bool plCheck = pl.user_level(http::client()->user_id().to_string(), create) >= pl.ban; + return plCheck; } bool Permissions::canKick() { - const bool plCheck = pl.user_level(http::client()->user_id().to_string()) >= pl.kick; - return plCheck || this->isV12Creator(); + const bool plCheck = pl.user_level(http::client()->user_id().to_string(), create) >= pl.kick; + return plCheck; } bool Permissions::canRedact() { - const bool plCheck = pl.user_level(http::client()->user_id().to_string()) >= pl.redact; - return plCheck || this->isV12Creator(); + const bool plCheck = pl.user_level(http::client()->user_id().to_string(), create) >= pl.redact; + return plCheck; } bool Permissions::canChange(int eventType) { - const bool plCheck = pl.user_level(http::client()->user_id().to_string()) >= + const bool plCheck = pl.user_level(http::client()->user_id().to_string(), create) >= pl.state_level(to_string(qml_mtx_events::fromRoomEventType( static_cast(eventType)))); - return plCheck || this->isV12Creator(); + return plCheck; } bool Permissions::canSend(int eventType) { - const bool plCheck = pl.user_level(http::client()->user_id().to_string()) >= + const bool plCheck = pl.user_level(http::client()->user_id().to_string(), create) >= pl.event_level(to_string(qml_mtx_events::fromRoomEventType( static_cast(eventType)))); - return plCheck || this->isV12Creator(); + return plCheck; } int @@ -94,15 +99,9 @@ Permissions::sendLevel(int eventType) bool Permissions::canPingRoom() { - const bool plCheck = pl.user_level(http::client()->user_id().to_string()) >= + const bool plCheck = pl.user_level(http::client()->user_id().to_string(), create) >= pl.notification_level(mtx::events::state::notification_keys::room); - return plCheck || this->isV12Creator(); + return plCheck; } -bool -Permissions::isV12Creator() -{ - return cache::client()->isV12Creator(this->roomId_.toStdString(), - http::client()->user_id().to_string()); -} #include "moc_Permissions.cpp" diff --git a/src/timeline/Permissions.h b/src/timeline/Permissions.h index ac36d5ea..9e2187a0 100644 --- a/src/timeline/Permissions.h +++ b/src/timeline/Permissions.h @@ -6,6 +6,8 @@ #include +#include +#include #include class TimelineModel; @@ -28,16 +30,20 @@ public: Q_INVOKABLE int redactLevel(); Q_INVOKABLE int changeLevel(int eventType); Q_INVOKABLE int sendLevel(int eventType); + Q_INVOKABLE qint64 creatorLevel() const { return mtx::events::state::Creator; } Q_INVOKABLE bool canPingRoom(); void invalidate(); const mtx::events::state::PowerLevels &powerlevelEvent() const { return pl; }; + const mtx::events::StateEvent &createEvent() const + { + return create; + }; private: - bool isV12Creator(); - QString roomId_; mtx::events::state::PowerLevels pl; + mtx::events::StateEvent create; }; diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 7cb19c70..b00d7d8f 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -605,8 +605,8 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r case UserName: return QVariant(displayName(QString::fromStdString(acc::sender(event)))); case UserPowerlevel: { - return static_cast( - permissions_.powerlevelEvent().user_level(acc::sender(event))); + return static_cast(permissions_.powerlevelEvent().user_level( + acc::sender(event), permissions_.createEvent())); } case Day: { @@ -1413,12 +1413,6 @@ TimelineModel::readEvent(const std::string &id) !UserSettings::instance()->readReceipts()); } -bool -TimelineModel::isV12Creator(const QString &id) const -{ - return cache::isV12Creator(this->roomId().toStdString(), id.toStdString()); -} - QString TimelineModel::displayName(const QString &id) const { @@ -2427,6 +2421,7 @@ QString TimelineModel::formatPowerLevelEvent( const mtx::events::StateEvent &event) const { + const auto create = permissions_.createEvent(); mtx::events::StateEvent const *prevEvent = nullptr; if (!event.unsigned_data.replaces_state.empty()) { auto tempPrevEvent = events.get(event.unsigned_data.replaces_state, event.event_id); @@ -2446,15 +2441,15 @@ TimelineModel::formatPowerLevelEvent( if (!prevEvent) return tr("%1 has changed the room's permissions.").arg(sender_name); - auto calc_affected = [&event, - &prevEvent](int64_t newPowerlevelSetting) -> std::pair { + auto calc_affected = + [&event, &prevEvent, &create](int64_t newPowerlevelSetting) -> std::pair { QStringList affected{}; auto numberOfAffected = 0; // We do only compare to people with explicit PL. Usually others are not going to be // affected either way and this is cheaper to iterate over. for (auto const &[mxid, currentPowerlevel] : event.content.users) { if (currentPowerlevel == newPowerlevelSetting && - prevEvent->content.user_level(mxid) < newPowerlevelSetting) { + prevEvent->content.user_level(mxid, create) < newPowerlevelSetting) { numberOfAffected++; if (numberOfAffected <= 2) { affected.push_back(QString::fromStdString(mxid)); @@ -2631,24 +2626,25 @@ TimelineModel::formatPowerLevelEvent( // Compare if a Powerlevel of a user changed for (auto const &[mxid, powerlevel] : event.content.users) { auto nameOfChangedUser = utils::replaceEmoji(displayName(QString::fromStdString(mxid))); - if (prevEvent->content.user_level(mxid) != powerlevel) { + if (prevEvent->content.user_level(mxid, create) != powerlevel) { if (powerlevel >= administrator_power_level) { resultingMessage.append(tr("%1 has made %2 an administrator of this room.") .arg(sender_name, nameOfChangedUser)); } else if (powerlevel >= moderator_power_level && - powerlevel > prevEvent->content.user_level(mxid)) { + powerlevel > prevEvent->content.user_level(mxid, create)) { resultingMessage.append(tr("%1 has made %2 a moderator of this room.") .arg(sender_name, nameOfChangedUser)); } else if (powerlevel >= moderator_power_level && - powerlevel < prevEvent->content.user_level(mxid)) { + powerlevel < prevEvent->content.user_level(mxid, create)) { resultingMessage.append(tr("%1 has downgraded %2 to moderator of this room.") .arg(sender_name, nameOfChangedUser)); } else { - resultingMessage.append(tr("%1 has changed the powerlevel of %2 from %3 to %4.") - .arg(sender_name, - nameOfChangedUser, - QString::number(prevEvent->content.user_level(mxid)), - QString::number(powerlevel))); + resultingMessage.append( + tr("%1 has changed the powerlevel of %2 from %3 to %4.") + .arg(sender_name, + nameOfChangedUser, + QString::number(prevEvent->content.user_level(mxid, create)), + QString::number(powerlevel))); } } } @@ -3379,6 +3375,7 @@ TimelineModel::pushrulesRoomContext() const cache::displayName(room_id_.toStdString(), http::client()->user_id().to_string()), .member_count = cache::client()->memberCount(room_id_.toStdString()), .power_levels = permissions_.powerlevelEvent(), + .create = permissions_.createEvent(), }; } diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index bae4533f..db7cee53 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -302,7 +302,6 @@ public: static QString getBareRoomLink(const QString &); static QString getRoomVias(const QString &); - Q_INVOKABLE bool isV12Creator(const QString &id) const; Q_INVOKABLE QString displayName(const QString &id) const; Q_INVOKABLE QString avatarUrl(const QString &id) const; Q_INVOKABLE QString formatDateSeparator(QDate date) const;