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( FetchContent_Declare(
MatrixClient MatrixClient
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git 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_EXAMPLES OFF CACHE INTERNAL "")
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")

View file

@ -213,8 +213,8 @@ modules:
- -DBUILD_SHARED_LIBS=OFF - -DBUILD_SHARED_LIBS=OFF
buildsystem: cmake-ninja buildsystem: cmake-ninja
sources: sources:
- commit: 15b43844f4ec27faa5f2ec92c4ded313206763aa - commit: 873911e352a0845dfb178f77b1ddea796a5d3455
tag: v0.10.1 #tag: v0.10.1
type: git type: git
url: https://github.com/Nheko-Reborn/mtxclient.git url: https://github.com/Nheko-Reborn/mtxclient.git
- name: nheko - name: nheko

View file

@ -89,7 +89,6 @@ Column {
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
isV12Creator: room.isV12Creator(userId)
powerlevel: userPowerlevel powerlevel: userPowerlevel
height: fontMetrics.ascent height: fontMetrics.ascent
width: height width: height
@ -98,7 +97,7 @@ Column {
sourceSize.height: height sourceSize.height: height
permissions: room ? room.permissions : null permissions: room ? room.permissions : null
visible: isAdmin || isModerator || isV12Creator visible: isAdmin || isModerator // implicitly includes creators as well
} }
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay

View file

@ -7,10 +7,10 @@ import QtQuick.Controls
import im.nheko import im.nheko
Image { Image {
required property int powerlevel required property var powerlevel
required property var permissions 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 isAdmin: permissions ? permissions.changeLevel(MtxEvent.PowerLevels) <= powerlevel : false
readonly property bool isModerator: permissions ? permissions.redactLevel() <= powerlevel : false readonly property bool isModerator: permissions ? permissions.redactLevel() <= powerlevel : false
readonly property bool isDefault: permissions ? permissions.defaultLevel() <= powerlevel : false readonly property bool isDefault: permissions ? permissions.defaultLevel() <= powerlevel : false
@ -27,14 +27,15 @@ Image {
source: sourceUrl + (ma.hovered ? palette.highlight : palette.buttonText) source: sourceUrl + (ma.hovered ? palette.highlight : palette.buttonText)
ToolTip.visible: ma.hovered ToolTip.visible: ma.hovered
ToolTip.text: { ToolTip.text: {
let pl = powerlevel.toLocaleString(Qt.locale(), "f", 0);
if (isV12Creator) if (isV12Creator)
return qsTr("Creator"); return qsTr("Creator");
else if (isAdmin) else if (isAdmin)
return qsTr("Administrator: %1").arg(powerlevel); return qsTr("Administrator (%1)").arg(pl)
else if (isModerator) else if (isModerator)
return qsTr("Moderator: %1").arg(powerlevel); return qsTr("Moderator: %1").arg(pl);
else else
return qsTr("User: %1").arg(powerlevel); return qsTr("User: %1").arg(pl);
} }
HoverHandler { HoverHandler {

View file

@ -94,14 +94,17 @@ ApplicationWindow {
Text { Text {
visible: !model.isType; visible: !model.isType;
text: { text: {
let pl = model.powerlevel.toLocaleString(Qt.locale(), "f", 0);
if (editingModel.creatorLevel == model.powerlevel)
return qsTr("Creator")
if (editingModel.adminLevel == model.powerlevel) if (editingModel.adminLevel == model.powerlevel)
return qsTr("Administrator (%1)").arg(model.powerlevel) return qsTr("Administrator (%1)").arg(pl)
else if (editingModel.moderatorLevel == model.powerlevel) 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) else if (editingModel.defaultUserLevel == model.powerlevel)
return qsTr("User (%1)").arg(model.powerlevel) return qsTr("User (%1)").arg(pl)
else else
return qsTr("Custom (%1)").arg(model.powerlevel) return qsTr("Custom (%1)").arg(pl)
} }
color: palette.text color: palette.text
} }
@ -138,7 +141,7 @@ ApplicationWindow {
color: palette.text color: palette.text
Keys.onPressed: { Keys.onPressed: event => {
if (typeEntry.text.includes('.') && event.matches(StandardKey.InsertParagraphSeparator)) { if (typeEntry.text.includes('.') && event.matches(StandardKey.InsertParagraphSeparator)) {
editingModel.types.add(typeEntry.index, typeEntry.text) editingModel.types.add(typeEntry.index, typeEntry.text)
typeEntry.visible = false; typeEntry.visible = false;
@ -334,12 +337,17 @@ ApplicationWindow {
Text { Text {
visible: !model.isUser; visible: !model.isUser;
text: { text: {
let pl = model.powerlevel.toLocaleString(Qt.locale(), "f", 0);
if (editingModel.creatorLevel == model.powerlevel)
return qsTr("Creator")
if (editingModel.adminLevel == model.powerlevel) if (editingModel.adminLevel == model.powerlevel)
return qsTr("Administrator (%1)").arg(model.powerlevel) return qsTr("Administrator (%1)").arg(pl)
else if (editingModel.moderatorLevel == model.powerlevel) 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 else
return qsTr("Custom (%1)").arg(model.powerlevel) return qsTr("Custom (%1)").arg(pl)
} }
color: palette.text color: palette.text
} }
@ -349,7 +357,7 @@ ApplicationWindow {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.rightMargin: 2 Layout.rightMargin: 2
image: model.isUser ? ":/icons/icons/ui/dismiss.svg" : ":/icons/icons/ui/add-square-button.svg" 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 hoverEnabled: true
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: model.isUser ? qsTr("Remove user") : qsTr("Add user") ToolTip.text: model.isUser ? qsTr("Remove user") : qsTr("Add user")

View file

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

View file

@ -4621,12 +4621,14 @@ Cache::updateSpaces(lmdb::txn &txn,
event.state_key.at(0) == '!') { event.state_key.at(0) == '!') {
const std::string &space = event.state_key; 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); auto pls = getStateEvent<mtx::events::state::PowerLevels>(txn, space);
if (!pls) if (!pls)
continue; continue;
if (pls->content.user_level(event.sender) >= if (pls->content.user_level(event.sender, create) >=
pls->content.state_level(space_event_type)) { pls->content.state_level(space_event_type)) {
db->spacesChildren.put(txn, space, room); db->spacesChildren.put(txn, space, room);
db->spacesParents.put(txn, room, space); db->spacesParents.put(txn, room, space);
@ -4635,7 +4637,7 @@ Cache::updateSpaces(lmdb::txn &txn,
room, room,
space, space,
event.sender, event.sender,
pls->content.user_level(event.sender), pls->content.user_level(event.sender, create),
pls->content.state_level(space_event_type)); 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; 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 bool
Cache::hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &eventTypes, Cache::hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &eventTypes,
const std::string &room_id, const std::string &room_id,
@ -4908,31 +4865,23 @@ Cache::hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &eventTypes
try { try {
auto db_ = getStatesDb(txn, room_id); 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 min_event_level = std::numeric_limits<int64_t>::max();
int64_t user_level = std::numeric_limits<int64_t>::min(); int64_t user_level = std::numeric_limits<int64_t>::min();
std::string_view event;
bool res = db_.get(txn, to_string(EventType::RoomPowerLevels), event);
if (res) {
try { try {
StateEvent<PowerLevels> msg = StateEvent<Create> create = getStateEvent<mtx::events::state::Create>(txn, room_id)
nlohmann::json::parse(std::string_view(event.data(), event.size())) .value_or(StateEvent<Create>{});
.get<StateEvent<PowerLevels>>(); StateEvent<PowerLevels> pls =
getStateEvent<mtx::events::state::PowerLevels>(txn, room_id)
.value_or(StateEvent<PowerLevels>{});
user_level = msg.content.user_level(user_id); user_level = pls.content.user_level(user_id, create);
for (const auto &ty : eventTypes) for (const auto &ty : eventTypes)
min_event_level = min_event_level = std::min(min_event_level, pls.content.state_level(to_string(ty)));
std::min(min_event_level, msg.content.state_level(to_string(ty)));
} catch (const nlohmann::json::exception &e) { } catch (const nlohmann::json::exception &e) {
nhlog::db()->warn("failed to parse m.room.power_levels event: {}", e.what()); nhlog::db()->warn("failed to parse m.room.power_levels event: {}", e.what());
} }
}
return user_level >= min_event_level; return user_level >= min_event_level;
} catch (...) { } catch (...) {
@ -6178,13 +6127,6 @@ roomMembers(const std::string &room_id)
return instance_->roomMembers(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 //! Check if the given user has power level greater than
//! lowest power level of the given events. //! lowest power level of the given events.
bool 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::Name)
NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::PinnedEvents) 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::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::ServerAcl)
NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::space::Child) NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::space::Child)
NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::space::Parent) NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::space::Parent)

View file

@ -123,10 +123,6 @@ runMigrations();
std::vector<std::string> std::vector<std::string>
roomMembers(const std::string &room_id); 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 //! Check if the given user has power level greater than than
//! lowest power level of the given events. //! lowest power level of the given events.
bool bool

View file

@ -145,10 +145,6 @@ public:
//! Retrieve all the user ids from a room. //! Retrieve all the user ids from a room.
std::vector<std::string> roomMembers(const std::string &room_id); 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 //! Check if the given user has power leve greater than than
//! lowest power level of the given events. //! lowest power level of the given events.
bool hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &eventTypes, 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()) ->getStateEvent<mtx::events::state::PowerLevels>(room_id_.toStdString())
.value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{}) .value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
.content} .content}
, create_{cache::client()
->getStateEvent<mtx::events::state::Create>(room_id_.toStdString())
.value_or(mtx::events::StateEvent<mtx::events::state::Create>{})}
{ {
try { try {
info_ = cache::singleRoomInfo(room_id_.toStdString()); info_ = cache::singleRoomInfo(room_id_.toStdString());
@ -92,7 +95,7 @@ MemberListBackend::data(const QModelIndex &index, int role) const
} }
case Powerlevel: case Powerlevel:
return static_cast<qlonglong>( 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: default:
return {}; return {};
} }
@ -172,28 +175,4 @@ MemberList::filterAcceptsRow(int source_row, const QModelIndex &) const
Qt::CaseInsensitive); 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" #include "moc_MemberList.cpp"

View file

@ -73,6 +73,7 @@ private:
bool loadingMoreMembers_{false}; bool loadingMoreMembers_{false};
mtx::events::state::PowerLevels powerLevels_; mtx::events::state::PowerLevels powerLevels_;
mtx::events::StateEvent<mtx::events::state::Create> create_;
friend class MemberList; friend class MemberList;
}; };
@ -122,7 +123,6 @@ public slots:
protected: protected:
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
private: private:
QString filterString; QString filterString;

View file

@ -17,12 +17,15 @@
#include "Logging.h" #include "Logging.h"
#include "MatrixClient.h" #include "MatrixClient.h"
PowerlevelsTypeListModel::PowerlevelsTypeListModel(const std::string &rid, PowerlevelsTypeListModel::PowerlevelsTypeListModel(
const std::string &rid,
const mtx::events::state::PowerLevels &pl, const mtx::events::state::PowerLevels &pl,
const mtx::events::StateEvent<mtx::events::state::Create> &create,
QObject *parent) QObject *parent)
: QAbstractListModel(parent) : QAbstractListModel(parent)
, room_id(rid) , room_id(rid)
, powerLevels_(pl) , powerLevels_(pl)
, create_(create)
{ {
std::set<mtx::events::state::power_level_t> seen_levels; std::set<mtx::events::state::power_level_t> seen_levels;
for (const auto &[type, level] : powerLevels_.events) { for (const auto &[type, level] : powerLevels_.events) {
@ -40,6 +43,9 @@ PowerlevelsTypeListModel::PowerlevelsTypeListModel(const std::string &rid,
seen_levels.insert(level); seen_levels.insert(level);
} }
} }
if (create_.content.room_version_creators_with_infinite_power()) {
seen_levels.insert(mtx::events::state::Creator);
}
for (const auto &level : { for (const auto &level : {
powerLevels_.events_default, powerLevels_.events_default,
@ -354,12 +360,15 @@ PowerlevelsTypeListModel::moveRows(const QModelIndex &,
return true; return true;
} }
PowerlevelsUserListModel::PowerlevelsUserListModel(const std::string &rid, PowerlevelsUserListModel::PowerlevelsUserListModel(
const std::string &rid,
const mtx::events::state::PowerLevels &pl, const mtx::events::state::PowerLevels &pl,
const mtx::events::StateEvent<mtx::events::state::Create> &create,
QObject *parent) QObject *parent)
: QAbstractListModel(parent) : QAbstractListModel(parent)
, room_id(rid) , room_id(rid)
, powerLevels_(pl) , powerLevels_(pl)
, create_(create)
{ {
std::set<mtx::events::state::power_level_t> seen_levels; std::set<mtx::events::state::power_level_t> seen_levels;
for (const auto &[user, level] : powerLevels_.users) { 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 : { for (const auto &level : {
powerLevels_.events_default, powerLevels_.events_default,
powerLevels_.state_default, powerLevels_.state_default,
@ -408,7 +427,7 @@ PowerlevelsUserListModel::toUsers() const
{ {
std::map<std::string, mtx::events::state::power_level_t, std::less<>> m; std::map<std::string, mtx::events::state::power_level_t, std::less<>> m;
for (const auto &[key, pl] : std::as_const(users)) 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; m[key] = pl;
return m; return m;
} }
@ -459,7 +478,7 @@ PowerlevelsUserListModel::data(const QModelIndex &index, int role) const
case IsUser: case IsUser:
return !user.mxid.empty(); return !user.mxid.empty();
case Moveable: case Moveable:
return !user.mxid.empty(); return !user.mxid.empty() && user.pl != mtx::events::state::Creator;
case Removeable: case Removeable:
return !user.mxid.empty() && user.mxid.find('.') != std::string::npos; return !user.mxid.empty() && user.mxid.find('.') != std::string::npos;
} }
@ -554,7 +573,15 @@ PowerlevelsUserListModel::moveRows(const QModelIndex &,
if (users.at(sourceRow).mxid.empty()) if (users.at(sourceRow).mxid.empty())
return false; return false;
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; auto pl = users.at(destinationChild > 0 ? destinationChild - 1 : 0).pl;
if (pl == mtx::events::state::Creator)
return false;
auto sourceItem = users.takeAt(sourceRow); auto sourceItem = users.takeAt(sourceRow);
sourceItem.pl = pl; sourceItem.pl = pl;
@ -577,9 +604,12 @@ PowerlevelEditingModels::PowerlevelEditingModels(QString room_id, QObject *paren
->getStateEvent<mtx::events::state::PowerLevels>(room_id.toStdString()) ->getStateEvent<mtx::events::state::PowerLevels>(room_id.toStdString())
.value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{}) .value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
.content) .content)
, types_(room_id.toStdString(), powerLevels_, this) , create_(cache::client()
, users_(room_id.toStdString(), powerLevels_, this) ->getStateEvent<mtx::events::state::Create>(room_id.toStdString())
, spaces_(room_id.toStdString(), powerLevels_, this) .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()) , room_id_(room_id.toStdString())
{ {
connect(&types_, connect(&types_,
@ -678,8 +708,10 @@ samePl(const mtx::events::state::PowerLevels &a, const mtx::events::state::Power
b.redact); b.redact);
} }
PowerlevelsSpacesListModel::PowerlevelsSpacesListModel(const std::string &room_id_, PowerlevelsSpacesListModel::PowerlevelsSpacesListModel(
const std::string &room_id_,
const mtx::events::state::PowerLevels &pl, const mtx::events::state::PowerLevels &pl,
const mtx::events::StateEvent<mtx::events::state::Create> &create,
QObject *parent) QObject *parent)
: QAbstractListModel(parent) : QAbstractListModel(parent)
, room_id(std::move(room_id_)) , room_id(std::move(room_id_))
@ -687,7 +719,7 @@ PowerlevelsSpacesListModel::PowerlevelsSpacesListModel(const std::string &room_i
{ {
beginResetModel(); beginResetModel();
spaces.push_back(Entry{room_id, oldPowerLevels_, true}); spaces.push_back(Entry{room_id, oldPowerLevels_, create, true});
std::unordered_set<std::string> visited; 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); cache::client()->getStateEvent<mtx::events::state::space::Parent>(s, space);
if (parent && parent->content.via && !parent->content.via->empty() && if (parent && parent->content.via && !parent->content.via->empty() &&
parent->content.canonical) { 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{ spaces.push_back(
s, parentPl ? parentPl->content : mtx::events::state::PowerLevels{}, false}); Entry{s,
childPl ? childPl->content : mtx::events::state::PowerLevels{},
childCreate,
false});
addChildren(s); addChildren(s);
} }
} }
@ -813,7 +851,7 @@ PowerlevelsSpacesListModel::data(QModelIndex const &index, int role) const
auto entry = spaces.at(row); auto entry = spaces.at(row);
switch (role) { switch (role) {
case Roles::IsEditable: 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)); entry.pl.state_level(to_string(mtx::events::EventType::RoomPowerLevels));
case Roles::IsDifferentFromBase: case Roles::IsDifferentFromBase:
return !samePl(entry.pl, oldPowerLevels_); return !samePl(entry.pl, oldPowerLevels_);

View file

@ -29,8 +29,10 @@ public:
Removeable, Removeable,
}; };
explicit PowerlevelsTypeListModel(const std::string &room_id_, explicit PowerlevelsTypeListModel(
const std::string &room_id_,
const mtx::events::state::PowerLevels &pl, const mtx::events::state::PowerLevels &pl,
const mtx::events::StateEvent<mtx::events::state::Create> &create,
QObject *parent = nullptr); QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
@ -67,6 +69,7 @@ public:
std::string room_id; std::string room_id;
QVector<Entry> types; QVector<Entry> types;
mtx::events::state::PowerLevels powerLevels_; mtx::events::state::PowerLevels powerLevels_;
mtx::events::StateEvent<mtx::events::state::Create> create_;
}; };
class PowerlevelsUserListModel final : public QAbstractListModel class PowerlevelsUserListModel final : public QAbstractListModel
@ -88,8 +91,10 @@ public:
Removeable, Removeable,
}; };
explicit PowerlevelsUserListModel(const std::string &room_id_, explicit PowerlevelsUserListModel(
const std::string &room_id_,
const mtx::events::state::PowerLevels &pl, const mtx::events::state::PowerLevels &pl,
const mtx::events::StateEvent<mtx::events::state::Create> &create,
QObject *parent = nullptr); QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
@ -121,6 +126,7 @@ public:
std::string room_id; std::string room_id;
QVector<Entry> users; QVector<Entry> users;
mtx::events::state::PowerLevels powerLevels_; mtx::events::state::PowerLevels powerLevels_;
mtx::events::StateEvent<mtx::events::state::Create> create_;
}; };
class PowerlevelsSpacesListModel final : public QAbstractListModel class PowerlevelsSpacesListModel final : public QAbstractListModel
@ -147,8 +153,10 @@ public:
ApplyPermissions, ApplyPermissions,
}; };
explicit PowerlevelsSpacesListModel(const std::string &room_id_, explicit PowerlevelsSpacesListModel(
const std::string &room_id_,
const mtx::events::state::PowerLevels &pl, const mtx::events::state::PowerLevels &pl,
const mtx::events::StateEvent<mtx::events::state::Create> &create,
QObject *parent = nullptr); QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
@ -183,6 +191,7 @@ public:
std::string roomid; std::string roomid;
mtx::events::state::PowerLevels pl; mtx::events::state::PowerLevels pl;
mtx::events::StateEvent<mtx::events::state::Create> create;
bool apply = false; bool apply = false;
}; };
@ -203,6 +212,7 @@ class PowerlevelEditingModels final : public QObject
Q_PROPERTY(PowerlevelsUserListModel *users READ users CONSTANT) Q_PROPERTY(PowerlevelsUserListModel *users READ users CONSTANT)
Q_PROPERTY(PowerlevelsTypeListModel *types READ types CONSTANT) Q_PROPERTY(PowerlevelsTypeListModel *types READ types CONSTANT)
Q_PROPERTY(PowerlevelsSpacesListModel *spaces READ spaces 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 adminLevel READ adminLevel NOTIFY adminLevelChanged)
Q_PROPERTY(qlonglong moderatorLevel READ moderatorLevel NOTIFY moderatorLevelChanged) Q_PROPERTY(qlonglong moderatorLevel READ moderatorLevel NOTIFY moderatorLevelChanged)
Q_PROPERTY(qlonglong defaultUserLevel READ defaultUserLevel NOTIFY defaultUserLevelChanged) Q_PROPERTY(qlonglong defaultUserLevel READ defaultUserLevel NOTIFY defaultUserLevelChanged)
@ -222,6 +232,7 @@ public:
PowerlevelsUserListModel *users() { return &users_; } PowerlevelsUserListModel *users() { return &users_; }
PowerlevelsTypeListModel *types() { return &types_; } PowerlevelsTypeListModel *types() { return &types_; }
PowerlevelsSpacesListModel *spaces() { return &spaces_; } PowerlevelsSpacesListModel *spaces() { return &spaces_; }
qlonglong creatorLevel() const { return mtx::events::state::Creator; }
qlonglong adminLevel() const qlonglong adminLevel() const
{ {
return powerLevels_.state_level(to_string(mtx::events::EventType::RoomPowerLevels)); return powerLevels_.state_level(to_string(mtx::events::EventType::RoomPowerLevels));
@ -235,6 +246,7 @@ public:
Q_INVOKABLE void addRole(int pl); Q_INVOKABLE void addRole(int pl);
mtx::events::state::PowerLevels powerLevels_; mtx::events::state::PowerLevels powerLevels_;
mtx::events::StateEvent<mtx::events::state::Create> create_;
PowerlevelsTypeListModel types_; PowerlevelsTypeListModel types_;
PowerlevelsUserListModel users_; PowerlevelsUserListModel users_;
PowerlevelsSpacesListModel spaces_; PowerlevelsSpacesListModel spaces_;

View file

@ -1453,6 +1453,9 @@ utils::roomVias(const std::string &roomid)
auto powerlevels = auto powerlevels =
cache::client()->getStateEvent<mtx::events::state::PowerLevels>(roomid).value_or( cache::client()->getStateEvent<mtx::events::state::PowerLevels>(roomid).value_or(
mtx::events::StateEvent<mtx::events::state::PowerLevels>{}); 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); auto acls = cache::client()->getStateEvent<mtx::events::state::ServerAcl>(roomid);
std::vector<QRegularExpression> allowedServers; std::vector<QRegularExpression> allowedServers;
@ -1501,6 +1504,19 @@ utils::roomVias(const std::string &roomid)
std::set<std::string> users_with_high_pl_in_room; 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 // we should pick PL > 50, but imo that is broken, so we just pick users who have admins
// perm // 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) { for (const auto &user : powerlevels.content.users) {
if (user.second >= powerlevels.content.events_default && if (user.second >= powerlevels.content.events_default &&
user.second >= powerlevels.content.state_default) { user.second >= powerlevels.content.state_default) {
@ -1525,11 +1541,12 @@ utils::roomVias(const std::string &roomid)
}); });
// add the highest powerlevel user // add the highest powerlevel user
auto max_pl_user = std::max_element( auto max_pl_user = std::max_element(users_with_high_pl_in_room.begin(),
users_with_high_pl_in_room.begin(),
users_with_high_pl_in_room.end(), users_with_high_pl_in_room.end(),
[&pl_content = powerlevels.content](const std::string &a, const std::string &b) { [&pl_content = powerlevels.content, &create](
return pl_content.user_level(a) < pl_content.user_level(b); 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()) { if (max_pl_user != users_with_high_pl_in_room.end()) {
auto host = auto host =
@ -1705,11 +1722,15 @@ utils::updateSpaceVias()
auto spaceid = roomid.toStdString(); 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() if (auto pl = cache::client()
->getStateEvent<mtx::events::state::PowerLevels>(spaceid) ->getStateEvent<mtx::events::state::PowerLevels>(spaceid)
.value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{}) .value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
.content; .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; continue;
auto children = cache::client()->getChildRoomIds(spaceid); auto children = cache::client()->getChildRoomIds(spaceid);
@ -1748,12 +1769,16 @@ utils::updateSpaceVias()
parent->origin_server_ts < weekAgo && parent->origin_server_ts < weekAgo &&
// ignore unset spaces // ignore unset spaces
(parent->content.via && !parent->content.via->empty())) { (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 = if (auto pl =
cache::client() cache::client()
->getStateEvent<mtx::events::state::PowerLevels>(childid) ->getStateEvent<mtx::events::state::PowerLevels>(childid)
.value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{}) .value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
.content; .content;
pl.user_level(us) < pl.user_level(us, childCreate) <
pl.state_level(to_string(mtx::events::EventType::SpaceParent))) pl.state_level(to_string(mtx::events::EventType::SpaceParent)))
continue; continue;
@ -2041,11 +2066,15 @@ utils::removeExpiredEvents()
if (!asus->globalExpiry && !getExpEv(roomid)) if (!asus->globalExpiry && !getExpEv(roomid))
continue; 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() if (auto pl = cache::client()
->getStateEvent<mtx::events::state::PowerLevels>(roomid) ->getStateEvent<mtx::events::state::PowerLevels>(roomid)
.value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{}) .value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
.content; .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); nhlog::net()->warn("Can't react events in {}, not running expiration.", roomid);
continue; continue;
} }

View file

@ -4,6 +4,8 @@
#include "Permissions.h" #include "Permissions.h"
#include <algorithm>
#include "Cache_p.h" #include "Cache_p.h"
#include "MatrixClient.h" #include "MatrixClient.h"
#include "TimelineModel.h" #include "TimelineModel.h"
@ -22,50 +24,53 @@ Permissions::invalidate()
->getStateEvent<mtx::events::state::PowerLevels>(roomId_.toStdString()) ->getStateEvent<mtx::events::state::PowerLevels>(roomId_.toStdString())
.value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{}) .value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
.content; .content;
create = cache::client()
->getStateEvent<mtx::events::state::Create>(roomId_.toStdString())
.value_or(mtx::events::StateEvent<mtx::events::state::Create>{});
} }
bool bool
Permissions::canInvite() Permissions::canInvite()
{ {
const bool plCheck = pl.user_level(http::client()->user_id().to_string()) >= pl.invite; const bool plCheck = pl.user_level(http::client()->user_id().to_string(), create) >= pl.invite;
return plCheck || this->isV12Creator(); return plCheck;
} }
bool bool
Permissions::canBan() Permissions::canBan()
{ {
const bool plCheck = pl.user_level(http::client()->user_id().to_string()) >= pl.ban; const bool plCheck = pl.user_level(http::client()->user_id().to_string(), create) >= pl.ban;
return plCheck || this->isV12Creator(); return plCheck;
} }
bool bool
Permissions::canKick() Permissions::canKick()
{ {
const bool plCheck = pl.user_level(http::client()->user_id().to_string()) >= pl.kick; const bool plCheck = pl.user_level(http::client()->user_id().to_string(), create) >= pl.kick;
return plCheck || this->isV12Creator(); return plCheck;
} }
bool bool
Permissions::canRedact() Permissions::canRedact()
{ {
const bool plCheck = pl.user_level(http::client()->user_id().to_string()) >= pl.redact; const bool plCheck = pl.user_level(http::client()->user_id().to_string(), create) >= pl.redact;
return plCheck || this->isV12Creator(); return plCheck;
} }
bool bool
Permissions::canChange(int eventType) 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( pl.state_level(to_string(qml_mtx_events::fromRoomEventType(
static_cast<qml_mtx_events::EventType>(eventType)))); static_cast<qml_mtx_events::EventType>(eventType))));
return plCheck || this->isV12Creator(); return plCheck;
} }
bool bool
Permissions::canSend(int eventType) 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( pl.event_level(to_string(qml_mtx_events::fromRoomEventType(
static_cast<qml_mtx_events::EventType>(eventType)))); static_cast<qml_mtx_events::EventType>(eventType))));
return plCheck || this->isV12Creator(); return plCheck;
} }
int int
@ -94,15 +99,9 @@ Permissions::sendLevel(int eventType)
bool bool
Permissions::canPingRoom() 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); 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" #include "moc_Permissions.cpp"

View file

@ -6,6 +6,8 @@
#include <QObject> #include <QObject>
#include <mtx/events.hpp>
#include <mtx/events/create.hpp>
#include <mtx/events/power_levels.hpp> #include <mtx/events/power_levels.hpp>
class TimelineModel; class TimelineModel;
@ -28,16 +30,20 @@ public:
Q_INVOKABLE int redactLevel(); Q_INVOKABLE int redactLevel();
Q_INVOKABLE int changeLevel(int eventType); Q_INVOKABLE int changeLevel(int eventType);
Q_INVOKABLE int sendLevel(int eventType); Q_INVOKABLE int sendLevel(int eventType);
Q_INVOKABLE qint64 creatorLevel() const { return mtx::events::state::Creator; }
Q_INVOKABLE bool canPingRoom(); Q_INVOKABLE bool canPingRoom();
void invalidate(); void invalidate();
const mtx::events::state::PowerLevels &powerlevelEvent() const { return pl; }; const mtx::events::state::PowerLevels &powerlevelEvent() const { return pl; };
const mtx::events::StateEvent<mtx::events::state::Create> &createEvent() const
{
return create;
};
private: private:
bool isV12Creator();
QString roomId_; QString roomId_;
mtx::events::state::PowerLevels pl; 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: case UserName:
return QVariant(displayName(QString::fromStdString(acc::sender(event)))); return QVariant(displayName(QString::fromStdString(acc::sender(event))));
case UserPowerlevel: { case UserPowerlevel: {
return static_cast<qlonglong>( return static_cast<qlonglong>(permissions_.powerlevelEvent().user_level(
permissions_.powerlevelEvent().user_level(acc::sender(event))); acc::sender(event), permissions_.createEvent()));
} }
case Day: { case Day: {
@ -1413,12 +1413,6 @@ TimelineModel::readEvent(const std::string &id)
!UserSettings::instance()->readReceipts()); !UserSettings::instance()->readReceipts());
} }
bool
TimelineModel::isV12Creator(const QString &id) const
{
return cache::isV12Creator(this->roomId().toStdString(), id.toStdString());
}
QString QString
TimelineModel::displayName(const QString &id) const TimelineModel::displayName(const QString &id) const
{ {
@ -2427,6 +2421,7 @@ QString
TimelineModel::formatPowerLevelEvent( TimelineModel::formatPowerLevelEvent(
const mtx::events::StateEvent<mtx::events::state::PowerLevels> &event) const 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; mtx::events::StateEvent<mtx::events::state::PowerLevels> const *prevEvent = nullptr;
if (!event.unsigned_data.replaces_state.empty()) { if (!event.unsigned_data.replaces_state.empty()) {
auto tempPrevEvent = events.get(event.unsigned_data.replaces_state, event.event_id); auto tempPrevEvent = events.get(event.unsigned_data.replaces_state, event.event_id);
@ -2446,15 +2441,15 @@ TimelineModel::formatPowerLevelEvent(
if (!prevEvent) if (!prevEvent)
return tr("%1 has changed the room's permissions.").arg(sender_name); return tr("%1 has changed the room's permissions.").arg(sender_name);
auto calc_affected = [&event, auto calc_affected =
&prevEvent](int64_t newPowerlevelSetting) -> std::pair<QStringList, int> { [&event, &prevEvent, &create](int64_t newPowerlevelSetting) -> std::pair<QStringList, int> {
QStringList affected{}; QStringList affected{};
auto numberOfAffected = 0; auto numberOfAffected = 0;
// We do only compare to people with explicit PL. Usually others are not going to be // 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. // affected either way and this is cheaper to iterate over.
for (auto const &[mxid, currentPowerlevel] : event.content.users) { for (auto const &[mxid, currentPowerlevel] : event.content.users) {
if (currentPowerlevel == newPowerlevelSetting && if (currentPowerlevel == newPowerlevelSetting &&
prevEvent->content.user_level(mxid) < newPowerlevelSetting) { prevEvent->content.user_level(mxid, create) < newPowerlevelSetting) {
numberOfAffected++; numberOfAffected++;
if (numberOfAffected <= 2) { if (numberOfAffected <= 2) {
affected.push_back(QString::fromStdString(mxid)); affected.push_back(QString::fromStdString(mxid));
@ -2631,23 +2626,24 @@ TimelineModel::formatPowerLevelEvent(
// Compare if a Powerlevel of a user changed // Compare if a Powerlevel of a user changed
for (auto const &[mxid, powerlevel] : event.content.users) { for (auto const &[mxid, powerlevel] : event.content.users) {
auto nameOfChangedUser = utils::replaceEmoji(displayName(QString::fromStdString(mxid))); 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) { if (powerlevel >= administrator_power_level) {
resultingMessage.append(tr("%1 has made %2 an administrator of this room.") resultingMessage.append(tr("%1 has made %2 an administrator of this room.")
.arg(sender_name, nameOfChangedUser)); .arg(sender_name, nameOfChangedUser));
} else if (powerlevel >= moderator_power_level && } 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.") resultingMessage.append(tr("%1 has made %2 a moderator of this room.")
.arg(sender_name, nameOfChangedUser)); .arg(sender_name, nameOfChangedUser));
} else if (powerlevel >= moderator_power_level && } 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.") resultingMessage.append(tr("%1 has downgraded %2 to moderator of this room.")
.arg(sender_name, nameOfChangedUser)); .arg(sender_name, nameOfChangedUser));
} else { } else {
resultingMessage.append(tr("%1 has changed the powerlevel of %2 from %3 to %4.") resultingMessage.append(
tr("%1 has changed the powerlevel of %2 from %3 to %4.")
.arg(sender_name, .arg(sender_name,
nameOfChangedUser, nameOfChangedUser,
QString::number(prevEvent->content.user_level(mxid)), QString::number(prevEvent->content.user_level(mxid, create)),
QString::number(powerlevel))); QString::number(powerlevel)));
} }
} }
@ -3379,6 +3375,7 @@ TimelineModel::pushrulesRoomContext() const
cache::displayName(room_id_.toStdString(), http::client()->user_id().to_string()), cache::displayName(room_id_.toStdString(), http::client()->user_id().to_string()),
.member_count = cache::client()->memberCount(room_id_.toStdString()), .member_count = cache::client()->memberCount(room_id_.toStdString()),
.power_levels = permissions_.powerlevelEvent(), .power_levels = permissions_.powerlevelEvent(),
.create = permissions_.createEvent(),
}; };
} }

View file

@ -302,7 +302,6 @@ public:
static QString getBareRoomLink(const QString &); static QString getBareRoomLink(const QString &);
static QString getRoomVias(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 displayName(const QString &id) const;
Q_INVOKABLE QString avatarUrl(const QString &id) const; Q_INVOKABLE QString avatarUrl(const QString &id) const;
Q_INVOKABLE QString formatDateSeparator(QDate date) const; Q_INVOKABLE QString formatDateSeparator(QDate date) const;