Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F140126
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
11 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/client/power-levels-desc.cpp b/src/client/power-levels-desc.cpp
index 486c478..42f137c 100644
--- a/src/client/power-levels-desc.cpp
+++ b/src/client/power-levels-desc.cpp
@@ -1,127 +1,137 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include <event.hpp>
#include "validator.hpp"
#include "power-levels-desc.hpp"
namespace Kazv
{
static std::pair<bool, json> numericValidator(const json &j)
{
if (j.is_number()) {
return {true, json(j.template get<long long>())};
} else if (j.is_string()) {
auto str = j.template get<std::string>();
try {
std::size_t pos = 0;
auto num = std::stoll(str, &pos);
if (pos != str.size()) { // not entirely a number
return {false, json()};
}
return {true, json(num)};
} catch (const std::logic_error &) {
return {false, json()};
}
} else {
return {false, json()};
}
}
static std::pair<bool, json> eventsValidator(const json &j)
{
auto [ret, val] = numericValidator(j[1]);
return {ret, json::array({j[0], std::move(val)})};
}
static std::pair<bool, json> notificationsValidator(const json &j)
{
auto ret = json::object();
auto res = cast(ret, j, "room", numericValidator);
return {res, ret};
}
static std::pair<bool, json> usersValidator(const json &j)
{
auto [ret, val] = numericValidator(j[1]);
return {ret, json::array({j[0], std::move(val)})};
}
// https://spec.matrix.org/v1.8/client-server-api/#mroompower_levels
static const auto numericFields = immer::map<std::string, long long>{
{"ban", 50},
{"events_default", 0},
{"invite", 0},
{"kick", 50},
{"redact", 50},
{"state_default", 50},
{"users_default", 0},
};
struct PowerLevelsDesc::Private
{
Private(const Event &e)
: original(e)
, normalized(normalize(e))
{}
Event normalize(const Event &e)
{
auto content = e.content().get();
auto ret = json{
{"type", "m.power_levels"},
{"state_key", ""},
{"content", json::object()},
};
// cast numeric fields
for (const auto &[key, defaultValue] : numericFields) {
ret["content"][key] = defaultValue;
cast(ret["content"], content, key, numericValidator);
}
// cast events map
ret["content"]["events"] = json::object();
castObject(ret["content"], content, "events", eventsValidator, CastObjectStrategy::IgnoreInvalid);
// cast notifications map
cast(ret["content"], content, "notifications", notificationsValidator);
// cast users map
ret["content"]["users"] = json::object();
castObject(ret["content"], content, "users", usersValidator, CastObjectStrategy::IgnoreInvalid);
return Event(ret);
}
Event original;
Event normalized;
};
PowerLevelsDesc::PowerLevelsDesc(const Event &e)
: m_d(new Private(e))
{}
PowerLevelsDesc::~PowerLevelsDesc() = default;
KAZV_DEFINE_COPYABLE_UNIQUE_PTR(PowerLevelsDesc, m_d);
+ PowerLevels PowerLevelsDesc::powerLevelsOfUser(std::string userId) const
+ {
+ const auto content = m_d->normalized.content().get();
+ if (content["users"].contains(userId)) {
+ return content["users"][userId].template get<PowerLevels>();
+ } else {
+ return content["users_default"].template get<PowerLevels>();
+ }
+ }
+
bool PowerLevelsDesc::canSendMessage(std::string userId, std::string eventType) const
{
return true;
}
bool PowerLevelsDesc::canSendState(std::string userId, std::string eventType) const
{
return true;
}
Event PowerLevelsDesc::normalizedEvent() const
{
return m_d ? m_d->normalized : Event();
}
}
diff --git a/src/client/power-levels-desc.hpp b/src/client/power-levels-desc.hpp
index c00edeb..0003426 100644
--- a/src/client/power-levels-desc.hpp
+++ b/src/client/power-levels-desc.hpp
@@ -1,59 +1,72 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <libkazv-config.hpp>
#include <memory>
+#include <cstdint>
#include <copy-helper.hpp>
namespace Kazv
{
class Event;
+ using PowerLevels = std::int_fast64_t;
+
/**
* Represent a m.power_levels event.
*/
class PowerLevelsDesc
{
public:
/**
* Construct a PowerLevelsDesc from an event.
*/
PowerLevelsDesc(const Event &e);
~PowerLevelsDesc();
KAZV_DECLARE_COPYABLE(PowerLevelsDesc);
+ /**
+ * Get the power levels of a user in the room.
+ *
+ * @param userId The id of the user.
+ * @return The power levels of
+ */
+ PowerLevels powerLevelsOfUser(std::string userId) const;
+
/**
* Determine whether a user can send a non-state event.
*
* @param userId The id of the user.
* @param eventType The type of the event to send.
+ * @return Whether the user can send such an event.
*/
bool canSendMessage(std::string userId, std::string eventType) const;
/**
* Determine whether a user can send a state event.
*
* @param userId The id of the user.
* @param eventType The type of the event to send.
+ * @return Whether the user can send such an event.
*/
bool canSendState(std::string userId, std::string eventType) const;
/**
* Get the normalized event of this.
*
* @return The normalized event for this power levels desc.
*/
Event normalizedEvent() const;
private:
struct Private;
std::unique_ptr<Private> m_d;
};
}
diff --git a/src/tests/client/power-levels-desc-test.cpp b/src/tests/client/power-levels-desc-test.cpp
index 614d42c..e72809d 100644
--- a/src/tests/client/power-levels-desc-test.cpp
+++ b/src/tests/client/power-levels-desc-test.cpp
@@ -1,135 +1,149 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include <catch2/catch_test_macros.hpp>
#include <types.hpp>
#include <factory.hpp>
#include <power-levels-desc.hpp>
using namespace Kazv;
using namespace Kazv::Factory;
static auto examplePowerLevelsJson = R"({
"ban": 10,
"events": {
"m.room.message": 20
},
"events_default": 1,
"invite": 1,
"kick": 1,
"notifications": {"room": 99},
"redact": 49,
"state_default": 100,
"users": {
"@mew:example.com": 100
},
"users_default": 1
})"_json;
static auto examplePowerLevels = makeEvent(
withEventType("m.power_levels")
| withStateKey("")
| withEventContent(examplePowerLevelsJson)
);
TEST_CASE("Parse Power Levels", "[client][power-levels]")
{
auto powerLevels = PowerLevelsDesc(examplePowerLevels);
REQUIRE(powerLevels.normalizedEvent().content().get()
== examplePowerLevels.content().get());
SECTION("additional keys are discarded")
{
auto jsonWithAdditionalKeys = examplePowerLevelsJson;
jsonWithAdditionalKeys["moe.kazv.mxc.something-else"];
auto eventWithAdditionalKeys = makeEvent(
withEventType("m.power_levels")
| withStateKey("")
| withEventContent(jsonWithAdditionalKeys)
);
auto powerLevels = PowerLevelsDesc(eventWithAdditionalKeys);
REQUIRE(powerLevels.normalizedEvent().content().get()
== examplePowerLevels.content().get());
}
SECTION("additional keys in notifications are discarded")
{
auto jsonWithAdditionalKeys = examplePowerLevelsJson;
jsonWithAdditionalKeys["notifications"]["moe.kazv.mxc.something-else"];
auto eventWithAdditionalKeys = makeEvent(
withEventType("m.power_levels")
| withStateKey("")
| withEventContent(jsonWithAdditionalKeys)
);
auto powerLevels = PowerLevelsDesc(eventWithAdditionalKeys);
REQUIRE(powerLevels.normalizedEvent().content().get()
== examplePowerLevels.content().get());
}
SECTION("string values are converted to numbers")
{
auto jsonWithStringValues = examplePowerLevelsJson;
jsonWithStringValues["ban"] = "10";
auto eventWithStringValues = makeEvent(
withEventType("m.power_levels")
| withStateKey("")
| withEventContent(jsonWithStringValues)
);
auto powerLevels = PowerLevelsDesc(eventWithStringValues);
REQUIRE(powerLevels.normalizedEvent().content().get()
== examplePowerLevels.content().get());
}
SECTION("string values that cannot be converted are replaced by default values")
{
auto jsonWithStringValues = examplePowerLevelsJson;
jsonWithStringValues["ban"] = "10s";
auto expected = examplePowerLevelsJson;
expected["ban"] = 50;
auto eventWithStringValues = makeEvent(
withEventType("m.power_levels")
| withStateKey("")
| withEventContent(jsonWithStringValues)
);
auto powerLevels = PowerLevelsDesc(eventWithStringValues);
REQUIRE(powerLevels.normalizedEvent().content().get()
== expected);
}
SECTION("string values in events that cannot be converted are replaced by default values")
{
auto jsonWithStringValues = examplePowerLevelsJson;
jsonWithStringValues["events"]["ttt"] = "10s";
auto expected = examplePowerLevelsJson;
auto eventWithStringValues = makeEvent(
withEventType("m.power_levels")
| withStateKey("")
| withEventContent(jsonWithStringValues)
);
auto powerLevels = PowerLevelsDesc(eventWithStringValues);
REQUIRE(powerLevels.normalizedEvent().content().get()
== expected);
}
SECTION("string values in users that cannot be converted are replaced by default values")
{
auto jsonWithStringValues = examplePowerLevelsJson;
jsonWithStringValues["users"]["@ttt:example.com"] = "10s";
auto expected = examplePowerLevelsJson;
auto eventWithStringValues = makeEvent(
withEventType("m.power_levels")
| withStateKey("")
| withEventContent(jsonWithStringValues)
);
auto powerLevels = PowerLevelsDesc(eventWithStringValues);
REQUIRE(powerLevels.normalizedEvent().content().get()
== expected);
}
}
+
+TEST_CASE("powerLevelsOfUser()", "[client][power-levels]")
+{
+ auto powerLevels = PowerLevelsDesc(examplePowerLevels);
+ REQUIRE(powerLevels.powerLevelsOfUser("@mew:example.com") == 100);
+ REQUIRE(powerLevels.powerLevelsOfUser("@mewmew:example.com") == 1);
+
+ SECTION("default power levels object")
+ {
+ powerLevels = PowerLevelsDesc(json::object({}));
+ REQUIRE(powerLevels.powerLevelsOfUser("@mew:example.com") == 0);
+ REQUIRE(powerLevels.powerLevelsOfUser("@mewmew:example.com") == 0);
+ }
+}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 1:11 PM (17 h, 52 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
55192
Default Alt Text
(11 KB)
Attached To
Mode
rL libkazv
Attached
Detach File
Event Timeline
Log In to Comment