diff --git a/CMakeLists.txt b/CMakeLists.txt index 3476f51a..0eaa6783 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -618,7 +618,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG v0.10.1 + GIT_TAG d6f10427d1c5e5b1a45f426274f8d2e8dd0b64be ) set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") diff --git a/resources/qml/TimelineSectionHeader.qml b/resources/qml/TimelineSectionHeader.qml index 20dbaf6a..f504c463 100644 --- a/resources/qml/TimelineSectionHeader.qml +++ b/resources/qml/TimelineSectionHeader.qml @@ -89,6 +89,7 @@ Column { anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter + isV12Creator: room.isV12Creator(userId) powerlevel: userPowerlevel height: fontMetrics.ascent width: height @@ -97,7 +98,7 @@ Column { sourceSize.height: height permissions: room ? room.permissions : null - visible: isAdmin || isModerator + visible: isAdmin || isModerator || isV12Creator } ToolTip.delay: Nheko.tooltipDelay diff --git a/resources/qml/components/PowerlevelIndicator.qml b/resources/qml/components/PowerlevelIndicator.qml index 6a6d89af..07453828 100644 --- a/resources/qml/components/PowerlevelIndicator.qml +++ b/resources/qml/components/PowerlevelIndicator.qml @@ -9,13 +9,14 @@ import im.nheko Image { required property int powerlevel required property var permissions + required property bool isV12Creator 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 readonly property string sourceUrl: { - if (isAdmin) + if (isAdmin || isV12Creator) return "image://colorimage/:/icons/icons/ui/ribbon_star.svg?"; else if (isModerator) return "image://colorimage/:/icons/icons/ui/ribbon.svg?"; @@ -26,7 +27,9 @@ Image { source: sourceUrl + (ma.hovered ? palette.highlight : palette.buttonText) ToolTip.visible: ma.hovered ToolTip.text: { - if (isAdmin) + if (isV12Creator) + return qsTr("Creator"); + else if (isAdmin) return qsTr("Administrator: %1").arg(powerlevel); else if (isModerator) return qsTr("Moderator: %1").arg(powerlevel); diff --git a/resources/qml/dialogs/RoomMembers.qml b/resources/qml/dialogs/RoomMembers.qml index 95dc9fc3..f03fda96 100644 --- a/resources/qml/dialogs/RoomMembers.qml +++ b/resources/qml/dialogs/RoomMembers.qml @@ -168,6 +168,7 @@ 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 84cad3ab..ca8644ba 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -4851,6 +4851,51 @@ 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, @@ -4863,6 +4908,10 @@ 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(); @@ -6129,6 +6178,13 @@ 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 diff --git a/src/Cache.h b/src/Cache.h index 7c79881c..c6118c78 100644 --- a/src/Cache.h +++ b/src/Cache.h @@ -123,6 +123,10 @@ 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 89fdd843..ee7913d5 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -145,6 +145,10 @@ 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 1d939bfa..bc493165 100644 --- a/src/MemberList.cpp +++ b/src/MemberList.cpp @@ -172,4 +172,28 @@ 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 f1d39336..46d73b53 100644 --- a/src/MemberList.h +++ b/src/MemberList.h @@ -122,6 +122,7 @@ 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/timeline/Permissions.cpp b/src/timeline/Permissions.cpp index 2ef6e5cd..569bf2bc 100644 --- a/src/timeline/Permissions.cpp +++ b/src/timeline/Permissions.cpp @@ -27,39 +27,45 @@ Permissions::invalidate() bool Permissions::canInvite() { - return pl.user_level(http::client()->user_id().to_string()) >= pl.invite; + const bool plCheck = pl.user_level(http::client()->user_id().to_string()) >= pl.invite; + return plCheck || this->isV12Creator(); } bool Permissions::canBan() { - return pl.user_level(http::client()->user_id().to_string()) >= pl.ban; + const bool plCheck = pl.user_level(http::client()->user_id().to_string()) >= pl.ban; + return plCheck || this->isV12Creator(); } bool Permissions::canKick() { - return pl.user_level(http::client()->user_id().to_string()) >= pl.kick; + const bool plCheck = pl.user_level(http::client()->user_id().to_string()) >= pl.kick; + return plCheck || this->isV12Creator(); } bool Permissions::canRedact() { - return pl.user_level(http::client()->user_id().to_string()) >= pl.redact; + const bool plCheck = pl.user_level(http::client()->user_id().to_string()) >= pl.redact; + return plCheck || this->isV12Creator(); } bool Permissions::canChange(int eventType) { - return pl.user_level(http::client()->user_id().to_string()) >= - pl.state_level(to_string( - qml_mtx_events::fromRoomEventType(static_cast(eventType)))); + const bool plCheck = pl.user_level(http::client()->user_id().to_string()) >= + pl.state_level(to_string(qml_mtx_events::fromRoomEventType( + static_cast(eventType)))); + return plCheck || this->isV12Creator(); } bool Permissions::canSend(int eventType) { - return pl.user_level(http::client()->user_id().to_string()) >= - pl.event_level(to_string( - qml_mtx_events::fromRoomEventType(static_cast(eventType)))); + const bool plCheck = pl.user_level(http::client()->user_id().to_string()) >= + pl.event_level(to_string(qml_mtx_events::fromRoomEventType( + static_cast(eventType)))); + return plCheck || this->isV12Creator(); } int @@ -88,8 +94,15 @@ Permissions::sendLevel(int eventType) bool Permissions::canPingRoom() { - return pl.user_level(http::client()->user_id().to_string()) >= - pl.notification_level(mtx::events::state::notification_keys::room); + const bool plCheck = pl.user_level(http::client()->user_id().to_string()) >= + pl.notification_level(mtx::events::state::notification_keys::room); + return plCheck || this->isV12Creator(); } +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 05513524..ac36d5ea 100644 --- a/src/timeline/Permissions.h +++ b/src/timeline/Permissions.h @@ -36,6 +36,8 @@ public: const mtx::events::state::PowerLevels &powerlevelEvent() const { return pl; }; private: + bool isV12Creator(); + QString roomId_; mtx::events::state::PowerLevels pl; }; diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index d5645ac4..f4be6227 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -1414,6 +1414,12 @@ 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 { diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index ad9f574e..f28a4dd7 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -302,6 +302,7 @@ 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;