Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Join rule event #862

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ target_sources(${QUOTIENT_LIB_NAME} PUBLIC FILE_SET HEADERS BASE_DIRS .
Quotient/events/eventcontent.h
Quotient/events/eventrelation.h
Quotient/events/roomcreateevent.h
Quotient/events/roomjoinrulesevent.h
Quotient/events/roomtombstoneevent.h
Quotient/events/roommessageevent.h
Quotient/events/roommemberevent.h
Expand Down
106 changes: 106 additions & 0 deletions Quotient/events/roomjoinrulesevent.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-FileCopyrightText: 2025 James Graham <[email protected]>
// SPDX-License-Identifier: LGPL-2.1-or-later

#pragma once

#include <Quotient/events/stateevent.h>

namespace Quotient
{
namespace EventContent {
Q_NAMESPACE_EXPORT(QUOTIENT_API)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That doesn't quite work, unfortunately, because another header file might also need it and then we will end up with ODR violation at linking. For that reason, all namespace-level enumerations that need Q_ENUM are defined in quotient_common.h; it doesn't have Q_NAMESPACE_EXPORT for EventContent but I would argue that enum JoinRule is no different from, say, enum Membership and belongs to the main Quotient namespace.


//! \brief Definition of an allow AllowCondition
//!
//! \sa https://spec.matrix.org/latest/client-server-api/#mroomjoin_ruless
struct AllowCondition {
QString roomId;
QString type;
};

//! Enum representing the available room join rules
enum JoinRule {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always make sure your enums specify the underlying integer type. The standard leaves it for implementations to decide, so MSVC uses int if the type is omitted while GCC and both flavours of Clang use unsigned int - this is why the CI is failing on Windows now. I added the same integer type enum Membership uses but if you prefer uint8_t I won't mind :)

Suggested change
enum JoinRule {
enum JoinRule : uint16_t {

Public,
Knock,
Invite,
Private,
Restricted,
KnockRestricted,
};
Q_ENUM_NS(JoinRule)

[[maybe_unused]] constexpr std::array JoinRuleStrings {
"public"_L1,
"knock"_L1,
"invite"_L1,
"private"_L1,
"restricted"_L1,
"knock_restricted"_L1,
};

//! \brief The content of a join rule event
//!
//! \sa https://spec.matrix.org/latest/client-server-api/#mroomjoin_rules
struct JoinRuleContent {
JoinRule joinRule;
QList<AllowCondition> allow;
};
} // namespace EventContent

template<>
inline EventContent::AllowCondition fromJson(const QJsonObject& jo)
{
return EventContent::AllowCondition {
fromJson<QString>(jo["room_id"_L1]),
fromJson<QString>(jo["type"_L1])
};
}

template<>
inline auto toJson(const EventContent::AllowCondition& c)
{
QJsonObject jo;
addParam<IfNotEmpty>(jo, "room_id"_L1, c.roomId);
addParam<IfNotEmpty>(jo, "type"_L1, c.type);
return jo;
}

template<>
inline EventContent::JoinRuleContent fromJson(const QJsonObject& jo)
{
return EventContent::JoinRuleContent {
enumFromJsonString<EventContent::JoinRule>(jo["join_rule"_L1].toString(), EventContent::JoinRuleStrings).value_or(EventContent::Public),
fromJson<QList<EventContent::AllowCondition>>(jo["allow"_L1])
};
}

template<>
inline auto toJson(const EventContent::JoinRuleContent& c)
{
QJsonObject jo;
addParam<IfNotEmpty>(jo, "join_rule"_L1, enumToJsonString<EventContent::JoinRule>(c.joinRule, EventContent::JoinRuleStrings));
addParam<IfNotEmpty>(jo, "allow"_L1, c.allow);
return jo;
}

//! \brief Class to define a join rule state event.
//!
//! \sa Quotient::StateEvent, https://spec.matrix.org/latest/client-server-api/#mroomjoin_rules
class JoinRulesEvent : public KeylessStateEventBase<JoinRulesEvent,
EventContent::JoinRuleContent>
{
public:
QUO_EVENT(JoinRulesEvent, "m.room.join_rules")
using KeylessStateEventBase::KeylessStateEventBase;

//! \brief The join rule for the room.
//!
//! \sa https://spec.matrix.org/latest/client-server-api/#mroomjoin_rules
EventContent::JoinRule joinRule() const { return content().joinRule; }

//! \brief The allow rules for restricted rooms.
//!
//! \sa https://spec.matrix.org/latest/client-server-api/#mroomjoin_rules
QList<EventContent::AllowCondition> allow() const { return content().allow; }
};
} // namespace Quotient
39 changes: 39 additions & 0 deletions Quotient/room.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
#include "events/roomavatarevent.h"
#include "events/roomcanonicalaliasevent.h"
#include "events/roomcreateevent.h"
#include "events/roomjoinrulesevent.h"
#include "events/roommemberevent.h"
#include "events/roompowerlevelsevent.h"
#include "events/roomtombstoneevent.h"
Expand Down Expand Up @@ -1534,6 +1535,36 @@ RoomStateView Room::currentState() const
return d->currentState;
}

EventContent::JoinRule Room::joinRule() const
{
return currentState().queryOr(&JoinRulesEvent::joinRule, EventContent::Public);
}

QList<QString> Room::allowIds() const
{
QList<QString> allowIds;
for (const auto& allowCondition : currentState().queryOr(&JoinRulesEvent::allow, QList<EventContent::AllowCondition>())) {
allowIds.append(allowCondition.roomId);
}
return allowIds;
}

void Room::setJoinRule(EventContent::JoinRule newRule, const QList<QString>& allowedRooms)
{
if (memberEffectivePowerLevel() < powerLevelFor<JoinRulesEvent>()) {
return;
}

EventContent::JoinRule actualRule = (newRule == EventContent::Restricted || newRule == EventContent::KnockRestricted) && allowedRooms.isEmpty() ? EventContent::Invite : newRule;
QList<EventContent::AllowCondition> newAllow;
for (const auto& room :allowedRooms) {
newAllow.append({room, "m.room_membership"_L1});
}
setState<JoinRulesEvent>(actualRule, newAllow);
// Not emitting joinRuleChanged() here, since that would override the change
// in the UI with the *current* value, which is not the *new* value.
}

int Room::memberEffectivePowerLevel(const UserId& memberId) const
{
return currentState().get<RoomPowerLevelsEvent>()->powerLevelForUser(
Expand Down Expand Up @@ -3208,6 +3239,14 @@ Room::Change Room::Private::processStateEvent(const RoomEvent& curEvent,

return Change::Other;
},
[this, oldEvent](const JoinRulesEvent& evt) {
if (const auto* oldJRE = static_cast<const JoinRulesEvent*>(oldEvent);
oldJRE && oldJRE->content().joinRule != evt.content().joinRule
) {
emit q->joinRuleChanged();
}
return Change::Other;
},
Change::Other);
}

Expand Down
35 changes: 35 additions & 0 deletions Quotient/room.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "events/roommessageevent.h"
#include "events/roompowerlevelsevent.h"
#include "events/roomtombstoneevent.h"
#include <Quotient/events/roomjoinrulesevent.h>

#include <QtCore/QJsonObject>
#include <QtGui/QImage>
Expand Down Expand Up @@ -165,6 +166,8 @@ class QUOTIENT_API Room : public QObject {
Q_PROPERTY(QStringList tagNames READ tagNames NOTIFY tagsChanged)
Q_PROPERTY(bool isFavourite READ isFavourite NOTIFY tagsChanged STORED false)
Q_PROPERTY(bool isLowPriority READ isLowPriority NOTIFY tagsChanged STORED false)
Q_PROPERTY(EventContent::JoinRule joinRule READ joinRule WRITE setJoinRule NOTIFY joinRuleChanged)
Q_PROPERTY(QList<QString> allowIds READ allowIds NOTIFY joinRuleChanged)

Q_PROPERTY(GetRoomEventsJob* eventsHistoryJob READ eventsHistoryJob NOTIFY eventsHistoryJobChanged)
Q_PROPERTY(int requestedHistorySize READ requestedHistorySize NOTIFY eventsHistoryJobChanged)
Expand Down Expand Up @@ -672,6 +675,35 @@ class QUOTIENT_API Room : public QObject {
/// \brief Get the current room state
RoomStateView currentState() const;

//! \brief The current Join Rule for the room
//!
//! \sa https://spec.matrix.org/latest/client-server-api/#mroomjoin_rules
EventContent::JoinRule joinRule() const;

//! \brief Set the Join Rule for the room
//!
//! If the local user does not have a high enough power level the request is rejected.
//!
//! \param newRule the new JoinRule to apply to the room
//! \param allowedRooms only required when the join rule is restricted. This is a
//! list of room IDs that members of can join without an invite.
//! If the rule is restricted and this list is empty it is treated as a join
//! rule of invite instead.
//!
//! \note While any room ID is permitted it is designed to be only spaces that are
//! input. I.e. only memebers of space `x` can join this room.
//!
//! \sa https://spec.matrix.org/latest/client-server-api/#mroomjoin_rules
Q_INVOKABLE void setJoinRule(EventContent::JoinRule newRule, const QList<QString>& allowedRooms = {});

//! \brief The list of Room IDs for when the join rule is Restricted
//!
//! This value will be empty when the Join Rule is not Restricted or
//! Knock-Restricted.
//!
//! \sa https://spec.matrix.org/latest/client-server-api/#mroomjoin_rules
QList<QString> allowIds() const;

//! \brief The effective power level of the given member in the room
//!
//! This is normally the same as calling `RoomPowerLevelEvent::powerLevelForUser(userId)` but
Expand Down Expand Up @@ -896,6 +928,9 @@ public Q_SLOTS:
void topicChanged();
void avatarChanged();

//! \brief The join rule for the room has changed
void joinRuleChanged();

//! \brief A new member has joined the room
//!
//! This can be from any previous state or a member previously unknown to
Expand Down
Loading