Refactor v12 support to use new user_level helper from mtxclient

This commit is contained in:
Nicolas Werner 2026-02-20 01:32:37 +01:00
parent 3184ab464c
commit e3bc058845
No known key found for this signature in database
GPG key ID: C8D75E610773F2D9
18 changed files with 208 additions and 207 deletions

View file

@ -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 "")

View file

@ -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

View file

@ -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

View file

@ -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 {

View file

@ -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")

View file

@ -168,7 +168,6 @@ ApplicationWindow {
sourceSize.height: height
powerlevel: model.powerlevel
permissions: room.permissions
isV12Creator: room.isV12Creator(model.mxid)
}
EncryptionIndicator {

View file

@ -4621,12 +4621,14 @@ Cache::updateSpaces(lmdb::txn &txn,
event.state_key.at(0) == '!') {
const std::string &space = event.state_key;
auto create = getStateEvent<mtx::events::state::Create>(txn, space)
.value_or(mtx::events::StateEvent<mtx::events::state::Create>{});
auto pls = getStateEvent<mtx::events::state::PowerLevels>(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<Create> evt =
nlohmann::json::parse(create_event).get<StateEvent<Create>>();
if (evt.sender == user_id) {
return true;
}
const std::optional<std::vector<std::string>> &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<mtx::events::EventType> &eventTypes,
const std::string &room_id,
@ -4908,30 +4865,22 @@ Cache::hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &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<int64_t>::max();
int64_t user_level = std::numeric_limits<int64_t>::min();
std::string_view event;
bool res = db_.get(txn, to_string(EventType::RoomPowerLevels), event);
try {
StateEvent<Create> create = getStateEvent<mtx::events::state::Create>(txn, room_id)
.value_or(StateEvent<Create>{});
StateEvent<PowerLevels> pls =
getStateEvent<mtx::events::state::PowerLevels>(txn, room_id)
.value_or(StateEvent<PowerLevels>{});
if (res) {
try {
StateEvent<PowerLevels> msg =
nlohmann::json::parse(std::string_view(event.data(), event.size()))
.get<StateEvent<PowerLevels>>();
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)

View file

@ -123,10 +123,6 @@ runMigrations();
std::vector<std::string>
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

View file

@ -145,10 +145,6 @@ public:
//! Retrieve all the user ids from a room.
std::vector<std::string> 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<mtx::events::EventType> &eventTypes,

View file

@ -19,6 +19,9 @@ MemberListBackend::MemberListBackend(const QString &room_id, QObject *parent)
->getStateEvent<mtx::events::state::PowerLevels>(room_id_.toStdString())
.value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
.content}
, create_{cache::client()
->getStateEvent<mtx::events::state::Create>(room_id_.toStdString())
.value_or(mtx::events::StateEvent<mtx::events::state::Create>{})}
{
try {
info_ = cache::singleRoomInfo(room_id_.toStdString());
@ -92,7 +95,7 @@ MemberListBackend::data(const QModelIndex &index, int role) const
}
case Powerlevel:
return static_cast<qlonglong>(
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"

View file

@ -73,6 +73,7 @@ private:
bool loadingMoreMembers_{false};
mtx::events::state::PowerLevels powerLevels_;
mtx::events::StateEvent<mtx::events::state::Create> 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;

View file

@ -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<mtx::events::state::Create> &create,
QObject *parent)
: QAbstractListModel(parent)
, room_id(rid)
, powerLevels_(pl)
, create_(create)
{
std::set<mtx::events::state::power_level_t> 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<mtx::events::state::Create> &create,
QObject *parent)
: QAbstractListModel(parent)
, room_id(rid)
, powerLevels_(pl)
, create_(create)
{
std::set<mtx::events::state::power_level_t> 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<std::string, mtx::events::state::power_level_t, std::less<>> 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<mtx::events::state::PowerLevels>(room_id.toStdString())
.value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
.content)
, types_(room_id.toStdString(), powerLevels_, this)
, users_(room_id.toStdString(), powerLevels_, this)
, spaces_(room_id.toStdString(), powerLevels_, this)
, create_(cache::client()
->getStateEvent<mtx::events::state::Create>(room_id.toStdString())
.value_or(mtx::events::StateEvent<mtx::events::state::Create>{}))
, 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<mtx::events::state::Create> &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<std::string> visited;
@ -703,10 +735,16 @@ PowerlevelsSpacesListModel::PowerlevelsSpacesListModel(const std::string &room_i
cache::client()->getStateEvent<mtx::events::state::space::Parent>(s, space);
if (parent && parent->content.via && !parent->content.via->empty() &&
parent->content.canonical) {
auto parentPl = cache::client()->getStateEvent<mtx::events::state::PowerLevels>(s);
auto childPl = cache::client()->getStateEvent<mtx::events::state::PowerLevels>(s);
auto childCreate =
cache::client()->getStateEvent<mtx::events::state::Create>(s).value_or(
mtx::events::StateEvent<mtx::events::state::Create>{});
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_);

View file

@ -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<mtx::events::state::Create> &create,
QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &) const override { return static_cast<int>(types.size()); }
@ -67,6 +69,7 @@ public:
std::string room_id;
QVector<Entry> types;
mtx::events::state::PowerLevels powerLevels_;
mtx::events::StateEvent<mtx::events::state::Create> 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<mtx::events::state::Create> &create,
QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &) const override { return static_cast<int>(users.size()); }
@ -121,6 +126,7 @@ public:
std::string room_id;
QVector<Entry> users;
mtx::events::state::PowerLevels powerLevels_;
mtx::events::StateEvent<mtx::events::state::Create> 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<mtx::events::state::Create> &create,
QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &) const override { return static_cast<int>(spaces.size()); }
@ -183,6 +191,7 @@ public:
std::string roomid;
mtx::events::state::PowerLevels pl;
mtx::events::StateEvent<mtx::events::state::Create> 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<mtx::events::state::Create> create_;
PowerlevelsTypeListModel types_;
PowerlevelsUserListModel users_;
PowerlevelsSpacesListModel spaces_;

View file

@ -1453,6 +1453,9 @@ utils::roomVias(const std::string &roomid)
auto powerlevels =
cache::client()->getStateEvent<mtx::events::state::PowerLevels>(roomid).value_or(
mtx::events::StateEvent<mtx::events::state::PowerLevels>{});
auto create =
cache::client()->getStateEvent<mtx::events::state::Create>(roomid).value_or(
mtx::events::StateEvent<mtx::events::state::Create>{});
auto acls = cache::client()->getStateEvent<mtx::events::state::ServerAcl>(roomid);
std::vector<QRegularExpression> allowedServers;
@ -1501,6 +1504,19 @@ utils::roomVias(const std::string &roomid)
std::set<std::string> 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<mtx::identifiers::User>(user).hostname();
if (isHostAllowed(host))
users_with_high_pl.insert(user);
}
for (const auto &user : create.content.additional_creators) {
auto host = mtx::identifiers::parse<mtx::identifiers::User>(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<mtx::identifiers::User>(*max_pl_user).hostname();
@ -1705,11 +1722,15 @@ utils::updateSpaceVias()
auto spaceid = roomid.toStdString();
auto create = cache::client()->getStateEvent<mtx::events::state::Create>(spaceid).value_or(
mtx::events::StateEvent<mtx::events::state::Create>{});
if (auto pl = cache::client()
->getStateEvent<mtx::events::state::PowerLevels>(spaceid)
.value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
.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<mtx::events::state::Create>(spaceid).value_or(
mtx::events::StateEvent<mtx::events::state::Create>{});
if (auto pl =
cache::client()
->getStateEvent<mtx::events::state::PowerLevels>(childid)
.value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
.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<mtx::events::state::Create>(roomid).value_or(
mtx::events::StateEvent<mtx::events::state::Create>{});
if (auto pl = cache::client()
->getStateEvent<mtx::events::state::PowerLevels>(roomid)
.value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
.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;
}

View file

@ -4,6 +4,8 @@
#include "Permissions.h"
#include <algorithm>
#include "Cache_p.h"
#include "MatrixClient.h"
#include "TimelineModel.h"
@ -22,50 +24,53 @@ Permissions::invalidate()
->getStateEvent<mtx::events::state::PowerLevels>(roomId_.toStdString())
.value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
.content;
create = cache::client()
->getStateEvent<mtx::events::state::Create>(roomId_.toStdString())
.value_or(mtx::events::StateEvent<mtx::events::state::Create>{});
}
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<qml_mtx_events::EventType>(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<qml_mtx_events::EventType>(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"

View file

@ -6,6 +6,8 @@
#include <QObject>
#include <mtx/events.hpp>
#include <mtx/events/create.hpp>
#include <mtx/events/power_levels.hpp>
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<mtx::events::state::Create> &createEvent() const
{
return create;
};
private:
bool isV12Creator();
QString roomId_;
mtx::events::state::PowerLevels pl;
mtx::events::StateEvent<mtx::events::state::Create> create;
};

View file

@ -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<qlonglong>(
permissions_.powerlevelEvent().user_level(acc::sender(event)));
return static_cast<qlonglong>(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<mtx::events::state::PowerLevels> &event) const
{
const auto create = permissions_.createEvent();
mtx::events::StateEvent<mtx::events::state::PowerLevels> 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<QStringList, int> {
auto calc_affected =
[&event, &prevEvent, &create](int64_t newPowerlevelSetting) -> std::pair<QStringList, int> {
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(),
};
}

View file

@ -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;