Page MenuHomePhorge

No OneTemporary

Size
36 KB
Referenced Files
None
Subscribers
None
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a233809..0262b49 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,88 +1,90 @@
set(KAZV_DATA_DIR ${KDE_INSTALL_DATADIR})
set(KAZV_L10N_DIR ${KAZV_DATA_DIR}/l10n)
if(WIN32)
set(KAZV_IS_WINDOWS 1)
else()
set(KAZV_IS_WINDOWS 0)
endif()
configure_file(kazv-path-config.hpp.in kazv-path-config.hpp)
configure_file(kazv-version.cpp.in kazv-version.cpp)
configure_file(kazv-platform.hpp.in kazv-platform.hpp)
set(kazvprivlib_SRCS
qt-job-handler.cpp
qt-job.cpp
${CMAKE_CURRENT_BINARY_DIR}/kazv-version.cpp
matrix-sdk.cpp
matrix-room.cpp
matrix-room-list.cpp
matrix-room-timeline.cpp
matrix-room-member.cpp
matrix-room-member-list-model.cpp
+ matrix-event-reader.cpp
+ matrix-event-reader-list-model.cpp
matrix-event.cpp
meta-types.cpp
l10n-provider.cpp
qt-rand-adapter.cpp
helper.cpp
matrix-promise.cpp
kazv-util.cpp
matrix-sticker-pack.cpp
matrix-sticker.cpp
matrix-sticker-pack-list.cpp
matrix-sticker-pack-source.cpp
device-mgmt/matrix-device.cpp
device-mgmt/matrix-device-list.cpp
kazv-config.cpp
kazv-io-manager.cpp
kazv-io-job.cpp
kazv-file.cpp
upload-job-model.cpp
kazv-markdown.cpp
shortcuts/shortcut-util.cpp
register-types.cpp
)
ecm_qt_declare_logging_category(kazvprivlib_SRCS
HEADER kazv-log.hpp
IDENTIFIER kazvLog
CATEGORY_NAME moe.kazv.mxc.kazv
)
add_library(kazvprivlib STATIC ${kazvprivlib_SRCS})
target_link_libraries(kazvprivlib PUBLIC
libkazv::kazvall
Qt5::Core
Qt5::Network
Threads::Threads
Qt5::Gui Qt5::Qml Qt5::Quick Qt5::QuickControls2 Qt5::Svg Qt5::Concurrent Qt5::Widgets
KF5::ConfigCore KF5::KIOCore KF5::Notifications
${CMARK_TARGET_NAME}
)
target_include_directories(kazvprivlib PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
target_include_directories(kazvprivlib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/device-mgmt)
set(kazv_SRCS
main.cpp
resources.qrc
)
add_executable(kazv ${kazv_SRCS})
target_link_libraries(kazv
PRIVATE
kazvprivlib
)
install(TARGETS kazv ${KF5_INSTALL_TARGETS_DEFAULT_ARGS})
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/l10n/
DESTINATION ${KAZV_L10N_DIR}
FILES_MATCHING PATTERN "*.ftl"
PATTERN "*.json"
)
add_subdirectory(tests)
diff --git a/src/matrix-event-reader-list-model.cpp b/src/matrix-event-reader-list-model.cpp
new file mode 100644
index 0000000..0dd1d58
--- /dev/null
+++ b/src/matrix-event-reader-list-model.cpp
@@ -0,0 +1,38 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2020-2024 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <libkazv-config.hpp>
+#include <immer/config.hpp> // https://github.com/arximboldi/immer/issues/168
+
+#include "matrix-event-reader-list-model.hpp"
+#include "matrix-event-reader.hpp"
+
+using namespace Kazv;
+
+MatrixEventReaderListModel::MatrixEventReaderListModel(lager::reader<immer::flex_vector<std::tuple<Kazv::Event, Kazv::Timestamp>>> readers, QObject *parent)
+ : MatrixRoomMemberListModel(
+ readers.xform(containerMap(EventList{}, zug::map([](const auto &item) {
+ return std::get<0>(item);
+ }))),
+ parent
+ )
+ , m_readers(readers)
+{
+}
+
+MatrixEventReaderListModel::~MatrixEventReaderListModel() = default;
+
+MatrixEventReader *MatrixEventReaderListModel::at(int index) const
+{
+ auto reader = m_readers[index][lager::lenses::or_default].make();
+ return new MatrixEventReader(
+ reader.map([](const auto &r) {
+ return std::get<0>(r);
+ }),
+ reader.map([](const auto &r) {
+ return std::get<1>(r);
+ }));
+}
diff --git a/src/matrix-event-reader-list-model.hpp b/src/matrix-event-reader-list-model.hpp
new file mode 100644
index 0000000..63f4a6e
--- /dev/null
+++ b/src/matrix-event-reader-list-model.hpp
@@ -0,0 +1,29 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2020-2024 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#pragma once
+#include <libkazv-config.hpp>
+
+#include <immer/config.hpp> // https://github.com/arximboldi/immer/issues/168
+
+#include "matrix-room-member-list-model.hpp"
+
+class MatrixEventReader;
+
+class MatrixEventReaderListModel : public MatrixRoomMemberListModel
+{
+ Q_OBJECT
+ QML_ELEMENT
+ QML_UNCREATABLE("")
+
+ lager::reader<immer::flex_vector<std::tuple<Kazv::Event, Kazv::Timestamp>>> m_readers;
+
+public:
+ explicit MatrixEventReaderListModel(lager::reader<immer::flex_vector<std::tuple<Kazv::Event, Kazv::Timestamp>>> readers, QObject *parent = 0);
+ ~MatrixEventReaderListModel() override;
+
+ Q_INVOKABLE MatrixEventReader *at(int index) const;
+};
diff --git a/src/matrix-event-reader.cpp b/src/matrix-event-reader.cpp
new file mode 100644
index 0000000..b3630d2
--- /dev/null
+++ b/src/matrix-event-reader.cpp
@@ -0,0 +1,22 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2020-2024 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <libkazv-config.hpp>
+#include <immer/config.hpp> // https://github.com/arximboldi/immer/issues/168
+
+#include "matrix-event-reader.hpp"
+
+using namespace Kazv;
+
+MatrixEventReader::MatrixEventReader(lager::reader<Kazv::Event> memberEvent, lager::reader<Kazv::Timestamp> timestamp, QObject *parent)
+ : MatrixRoomMember(memberEvent, parent)
+ , LAGER_QT(timestamp)(timestamp.map([](auto ts) {
+ return static_cast<qint64>(ts);
+ }))
+{
+}
+
+MatrixEventReader::~MatrixEventReader() = default;
diff --git a/src/matrix-event-reader.hpp b/src/matrix-event-reader.hpp
new file mode 100644
index 0000000..1b80b69
--- /dev/null
+++ b/src/matrix-event-reader.hpp
@@ -0,0 +1,25 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2020-2024 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#pragma once
+#include <libkazv-config.hpp>
+
+#include <immer/config.hpp> // https://github.com/arximboldi/immer/issues/168
+
+#include "matrix-room-member.hpp"
+
+class MatrixEventReader : public MatrixRoomMember
+{
+ Q_OBJECT
+ QML_ELEMENT
+ QML_UNCREATABLE("")
+
+public:
+ explicit MatrixEventReader(lager::reader<Kazv::Event> memberEvent, lager::reader<Kazv::Timestamp> timestamp, QObject *parent = 0);
+ ~MatrixEventReader() override;
+
+ LAGER_QT_READER(qint64, timestamp);
+};
diff --git a/src/matrix-event.cpp b/src/matrix-event.cpp
index 7a6c9a6..c5fa69a 100644
--- a/src/matrix-event.cpp
+++ b/src/matrix-event.cpp
@@ -1,104 +1,136 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include <immer/config.hpp> // https://github.com/arximboldi/immer/issues/168
#include "matrix-event.hpp"
+#include "matrix-event-reader-list-model.hpp"
#include "helper.hpp"
using namespace Kazv;
static std::optional<LocalEchoDesc> getLocalEcho(std::variant<Event, LocalEchoDesc> event)
{
if (std::holds_alternative<LocalEchoDesc>(event)) {
return std::get<LocalEchoDesc>(event);
} else {
return std::nullopt;
}
}
static Event getEvent(std::variant<Event, LocalEchoDesc> event)
{
if (std::holds_alternative<LocalEchoDesc>(event)) {
return std::get<LocalEchoDesc>(event).event;
} else {
return std::get<Event>(event);
}
}
-MatrixEvent::MatrixEvent(lager::reader<std::variant<Event, LocalEchoDesc>> event, QObject *parent)
+MatrixEvent::MatrixEvent(lager::reader<std::variant<Event, LocalEchoDesc>> event, std::optional<Room> room, QObject *parent)
: QObject(parent)
, m_localEcho(event.map(getLocalEcho))
, m_event(event.map(getEvent))
+ , m_room(room)
, LAGER_QT(eventId)(m_event.xform(zug::map([](Event e) { return e.id(); }) | strToQt))
, LAGER_QT(sender)(m_event.xform(zug::map([](Event e) { return e.sender(); }) | strToQt))
, LAGER_QT(type)(m_event.xform(zug::map([](Event e) { return e.type(); }) | strToQt))
, LAGER_QT(stateKey)(m_event.xform(zug::map([](Event e) { return e.stateKey(); }) | strToQt))
, LAGER_QT(content)(m_event.xform(zug::map([](Event e) { return e.content().get().get<QJsonObject>(); })))
, LAGER_QT(encrypted)(m_event.xform(zug::map([](Event e) { return e.encrypted(); })))
, LAGER_QT(decrypted)(m_event.map([](Event e) { return e.decrypted(); }))
, LAGER_QT(isState)(m_event.map([](Event e) { return e.isState(); }))
, LAGER_QT(unsignedData)(m_event.map([](Event e) {
auto j = e.raw();
if (j.get().contains("unsigned")) {
return j.get()["unsigned"].template get<QJsonObject>();
} else {
return QJsonObject();
}
}))
, LAGER_QT(isLocalEcho)(m_localEcho.map([](const auto &maybe) { return maybe.has_value(); }))
, LAGER_QT(isSending)(m_localEcho.map([](const auto &maybe) {
return maybe.has_value() && maybe.value().status == LocalEchoDesc::Sending;
}))
, LAGER_QT(isFailed)(m_localEcho.map([](const auto &maybe) {
return maybe.has_value() && maybe.value().status == LocalEchoDesc::Failed;
}))
, LAGER_QT(txnId)(m_localEcho.map([](const auto &maybe) {
return maybe.has_value() ? QString::fromStdString(maybe.value().txnId) : QStringLiteral("");
}))
, LAGER_QT(redacted)(m_event.map([](const auto &e) {
return e.redacted();
}))
, LAGER_QT(originalSource)(m_event.map([](Event e) {
return e.originalJson().get().template get<QJsonObject>();
}))
, LAGER_QT(decryptedSource)(m_event.map([](Event e) {
return e.decrypted() ? e.raw().get().template get<QJsonObject>() : QJsonObject();
}))
, LAGER_QT(replyingToEventId)(m_event.map([](Event e) {
return e.replyingTo();
}).xform(strToQt))
, LAGER_QT(relationType)(m_event.map([](Event e) {
return e.relationship().first;
}).xform(strToQt))
, LAGER_QT(relatedEventId)(m_event.map([](Event e) {
return e.relationship().second;
}).xform(strToQt))
, LAGER_QT(formattedTime)(m_event
.map(&Event::originServerTs)
.map([](Timestamp ts) {
auto locale = QLocale::system();
return locale.toString(
QDateTime::fromMSecsSinceEpoch(ts).time(),
QLocale::ShortFormat
);
}))
{
}
-MatrixEvent::MatrixEvent(lager::reader<Event> event, QObject *parent)
- : MatrixEvent(event.map([](const auto &e) -> std::variant<Kazv::Event, Kazv::LocalEchoDesc> { return e; }), parent)
+MatrixEvent::MatrixEvent(lager::reader<Event> event, std::optional<Kazv::Room> room, QObject *parent)
+ : MatrixEvent(event.map([](const auto &e) -> std::variant<Kazv::Event, Kazv::LocalEchoDesc> { return e; }), room, parent)
{
}
MatrixEvent::~MatrixEvent() = default;
+MatrixEventReaderListModel *MatrixEvent::readers() const
+{
+ if (!m_room.has_value()) {
+ return new MatrixEventReaderListModel(lager::make_constant(immer::flex_vector<std::tuple<Kazv::Event, Kazv::Timestamp>>{}));
+ }
+
+ auto eventReaders = lager::with(
+ m_room.value().eventReaders(m_event.map(&Event::id)),
+ m_room.value().stateEvents()
+ ).map([](const auto &eventReaders, const auto &states) {
+ return intoImmer(
+ immer::flex_vector<std::tuple<Kazv::Event, Kazv::Timestamp>>{},
+ zug::map([states](const auto &item) -> std::tuple<Kazv::Event, Kazv::Timestamp> {
+ auto [userId, ts] = item;
+ auto memberEvent = states.count({"m.room.member", userId})
+ ? states[{"m.room.member", userId}]
+ : Event{json{
+ {"content", json::object()},
+ {"state_key", userId},
+ {"type", "m.room.member"},
+ }};
+ return {memberEvent, ts};
+ }),
+ eventReaders
+ );
+ }).make();
+
+ return new MatrixEventReaderListModel(eventReaders);
+}
+
Kazv::Event MatrixEvent::underlyingEvent() const
{
return m_event.get();
}
diff --git a/src/matrix-event.hpp b/src/matrix-event.hpp
index 95573c3..8ac992d 100644
--- a/src/matrix-event.hpp
+++ b/src/matrix-event.hpp
@@ -1,57 +1,62 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <libkazv-config.hpp>
#include <immer/config.hpp> // https://github.com/arximboldi/immer/issues/168
#include <QObject>
#include <QQmlEngine>
#include <lager/extra/qt.hpp>
#include <client/room/room.hpp>
#include "qt-json.hpp"
+class MatrixEventReaderListModel;
+
class MatrixEvent : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
lager::reader<std::optional<Kazv::LocalEchoDesc>> m_localEcho;
lager::reader<Kazv::Event> m_event;
+ std::optional<Kazv::Room> m_room;
public:
- explicit MatrixEvent(lager::reader<std::variant<Kazv::Event, Kazv::LocalEchoDesc>> event, QObject *parent = 0);
- explicit MatrixEvent(lager::reader<Kazv::Event> event, QObject *parent = 0);
+ explicit MatrixEvent(lager::reader<std::variant<Kazv::Event, Kazv::LocalEchoDesc>> event, std::optional<Kazv::Room> room = std::nullopt, QObject *parent = 0);
+ explicit MatrixEvent(lager::reader<Kazv::Event> event, std::optional<Kazv::Room> room = std::nullopt, QObject *parent = 0);
~MatrixEvent() override;
LAGER_QT_READER(QString, eventId);
LAGER_QT_READER(QString, sender);
LAGER_QT_READER(QString, type);
LAGER_QT_READER(QString, stateKey);
LAGER_QT_READER(QJsonObject, content);
LAGER_QT_READER(bool, encrypted);
LAGER_QT_READER(bool, decrypted);
LAGER_QT_READER(bool, isState);
LAGER_QT_READER(QJsonObject, unsignedData);
LAGER_QT_READER(bool, isLocalEcho);
LAGER_QT_READER(bool, isSending);
LAGER_QT_READER(bool, isFailed);
LAGER_QT_READER(QString, txnId);
LAGER_QT_READER(bool, redacted);
LAGER_QT_READER(QJsonObject, originalSource);
LAGER_QT_READER(QJsonObject, decryptedSource);
LAGER_QT_READER(QString, replyingToEventId);
LAGER_QT_READER(QString, relationType);
LAGER_QT_READER(QString, relatedEventId);
LAGER_QT_READER(QString, formattedTime);
+ MatrixEventReaderListModel *readers() const;
+
Kazv::Event underlyingEvent() const;
};
diff --git a/src/matrix-room-timeline.cpp b/src/matrix-room-timeline.cpp
index 96afa3c..ea9c344 100644
--- a/src/matrix-room-timeline.cpp
+++ b/src/matrix-room-timeline.cpp
@@ -1,104 +1,104 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include <immer/config.hpp> // https://github.com/arximboldi/immer/issues/168
#include <cmath>
#include <lager/lenses/optional.hpp>
#include <QTimer>
#include <cursorutil.hpp>
#include "matrix-room-timeline.hpp"
#include "matrix-event.hpp"
#include "helper.hpp"
#include "kazv-log.hpp"
using namespace Kazv;
MatrixRoomTimeline::MatrixRoomTimeline(Kazv::Room room, QObject *parent)
: QAbstractListModel(parent)
, m_room(room)
, m_timelineEventIds(m_room.timelineEventIds())
, m_messagesMap(m_room.messagesMap())
, m_internalCount(0)
, m_localEchoes(m_room.localEchoes())
, m_timelineGaps(m_room.timelineGaps())
, LAGER_QT(count)(lager::with(
m_timelineEventIds.xform(containerSize),
m_localEchoes.xform(containerSize)
).map([](const auto &tlSize, const auto &localSize) { return tlSize + localSize; }))
, LAGER_QT(gaps)(m_timelineGaps.map([](auto g) {
QSet<QString> res{};
for (const auto &[k, _v] : g) {
res.insert(QString::fromStdString(k));
}
return res;
}))
{
connect(this, &MatrixRoomTimeline::countChanged, this, &MatrixRoomTimeline::updateInternalCount);
// HACK: If internally the count is set to its full count at the beginning,
// ListView will instantiate everything in the timeline.
// I don't know why it is the case, so I keep the internal count as 0
// when it is constructed, and update it in the next tick.
QTimer::singleShot(0, this, [this] { Q_EMIT countChanged(count()); });
}
MatrixRoomTimeline::~MatrixRoomTimeline() = default;
MatrixEvent *MatrixRoomTimeline::at(int index) const
{
return new MatrixEvent(lager::with(m_timelineEventIds, m_messagesMap, m_localEchoes, LAGER_QT(count))
.map([index](const auto &tlEventIds, const auto &messagesMap, const auto &localEchoes, const auto &count) -> std::variant<Kazv::Event, Kazv::LocalEchoDesc> {
auto rIndex = count - index - 1;
if (static_cast<unsigned int>(rIndex) < tlEventIds.size()) {
return messagesMap[tlEventIds[rIndex]];
} else {
auto remainingIndex = rIndex - tlEventIds.size();
if (remainingIndex < localEchoes.size()) {
return localEchoes[remainingIndex];
} else {
return Event();
}
}
- }));
+ }), m_room);
}
int MatrixRoomTimeline::rowCount(const QModelIndex &index) const
{
if (index.isValid()) {
return 0;
} else {
return m_internalCount;
}
}
QVariant MatrixRoomTimeline::data(const QModelIndex &, int) const
{
return QVariant();
}
void MatrixRoomTimeline::updateInternalCount()
{
auto curCount = count();
auto oldCount = m_internalCount;
auto diff = std::abs(curCount - oldCount);
if (curCount > oldCount) {
beginInsertRows(QModelIndex(), 0, diff - 1);
m_internalCount = curCount;
endInsertRows();
} else if (curCount < oldCount) {
beginRemoveRows(QModelIndex(), 0, diff - 1);
m_internalCount = curCount;
endRemoveRows();
}
}
diff --git a/src/matrix-room.cpp b/src/matrix-room.cpp
index 1ce5eb1..8eb48c4 100644
--- a/src/matrix-room.cpp
+++ b/src/matrix-room.cpp
@@ -1,451 +1,452 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2024 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include <immer/config.hpp> // https://github.com/arximboldi/immer/issues/168
#include <lager/setter.hpp>
#include <nlohmann/json.hpp>
#include <event.hpp>
#include <QFile>
#include <QMimeDatabase>
#include "kazv-log.hpp"
#include "matrix-room.hpp"
#include "matrix-room-timeline.hpp"
#include "matrix-room-member.hpp"
#include "matrix-room-member-list-model.hpp"
#include "matrix-promise.hpp"
#include "matrix-event.hpp"
#include "kazv-markdown.hpp"
#include "qfunctionutils.hpp"
#include "helper.hpp"
using namespace Kazv;
static const int typingDebounceMs = 500;
static const int saveDraftDebounceMs = 5000;
static const int typingTimeoutMs = 1000;
MatrixRoom::MatrixRoom(Kazv::Room room, lager::reader<std::string> selfUserId, QObject *parent)
: QObject(parent)
, m_room(room)
, m_selfUserId(selfUserId)
, m_memberNames(m_room.members())
, m_powerLevels(m_room.powerLevels())
, LAGER_QT(roomId)(m_room.roomId().xform(strToQt))
, LAGER_QT(name)(m_room.nameOpt()[lager::lenses::or_default].xform(strToQt))
, LAGER_QT(heroNames)(m_room.heroDisplayNames().xform(strListToQt))
, LAGER_QT(avatarMxcUri)(m_room.avatarMxcUri().xform(strToQt))
, LAGER_QT(roomOrHeroAvatarMxcUri)(lager::with(m_room.heroMemberEvents(), m_room.avatarMxcUri())
.map([](const auto &heroes, const auto &roomAvatar) {
if (!roomAvatar.empty()) {
return roomAvatar;
}
if (heroes.size() == 1) {
return zug::reduce(roomMemberToAvatar(zug::last), std::string(), heroes);
}
return std::string();
})
.xform(strToQt))
, LAGER_QT(localDraft)(m_room.localDraft().xform(strToQt))
, LAGER_QT(encrypted)(m_room.encrypted())
, LAGER_QT(memberNames)(m_memberNames.xform(strListToQt))
, LAGER_QT(tagIds)(m_room.tags().map([](const auto &tagsMap) {
return zug::into(
QStringList(),
zug::map([](const auto pair) {
return QString::fromStdString(pair.first);
}),
tagsMap);
}))
, LAGER_QT(membership)(m_room.membership().map([](const auto &membership) { return static_cast<Membership>(membership); }))
, m_setTypingThrottled(QFunctionUtils::Throttle([self=QPointer<MatrixRoom>(this)]() {
if (self) {
self->setTypingImpl();
}
}, typingDebounceMs))
, m_updateLocalDraftDebounced(QFunctionUtils::Debounce([self=QPointer<MatrixRoom>(this)]() {
if (self) {
self->updateLocalDraftNow();
}
}, saveDraftDebounceMs))
, m_internalLocalDraft(std::nullopt)
{
lager::watch(m_powerLevels, [this](auto &&) {
powerLevelsChanged();
});
}
MatrixRoom::~MatrixRoom() {
updateLocalDraftNow();
}
MatrixRoomTimeline *MatrixRoom::timeline() const
{
return new MatrixRoomTimeline(m_room);
}
static void maybeAddRelations(nlohmann::json &msg, const QString &replyTo)
{
if (!replyTo.isEmpty()) {
msg["content"]["m.relates_to"] = nlohmann::json{
{"m.in_reply_to", {
{"event_id", replyTo},
}},
};
}
}
static const std::string HTML_FORMAT = "org.matrix.custom.html";
nlohmann::json makeTextMessageJson(const QString &text, const QString &replyTo, Event replyToEvent)
{
auto msg = nlohmann::json{
{"type", "m.room.message"},
{"content", {
{"msgtype", "m.text"},
{"body", text},
}},
};
std::string replyToBody;
if (!replyTo.isEmpty()) {
auto replyToContent = replyToEvent.content().get();
if (replyToContent.contains("format")
&& replyToContent["format"] == HTML_FORMAT
&& replyToContent.contains("formatted_body")
&& replyToContent["formatted_body"].is_string()) {
replyToBody = replyToContent["formatted_body"].template get<std::string>();
auto replyPos = replyToBody.find("</mx-reply>");
if (replyPos != std::string::npos) {
replyToBody.erase(0, replyPos + std::string("</mx-reply>").size());
}
} else if (replyToContent.contains("body") && replyToContent["body"].is_string()) {
replyToBody = markdownToHtml(replyToContent["body"].template get<std::string>()).html;
}
if (!replyToBody.empty()) {
replyToBody = "<mx-reply><blockquote>" + replyToBody + "</blockquote></mx-reply>";
}
}
auto richText = markdownToHtml(text.toStdString());
msg["content"]["format"] = HTML_FORMAT;
msg["content"]["formatted_body"] = replyToBody + richText.html;
auto mentions = richText.mentions;
if (!replyToEvent.sender().empty()
&& std::find(mentions.begin(), mentions.end(), replyToEvent.sender()) == mentions.end()) {
mentions = std::move(mentions).push_back(replyToEvent.sender());
}
if (!mentions.empty()) {
msg["content"]["m.mentions"] = {
{"user_ids", mentions},
};
}
maybeAddRelations(msg, replyTo);
return msg;
}
void MatrixRoom::sendMessage(const QJsonObject &eventJson, const QString &replyTo) const
{
auto msg = nlohmann::json(eventJson);
maybeAddRelations(msg, replyTo);
m_room.sendMessage(Event(msg));
}
void MatrixRoom::sendTextMessage(QString text, QString replyTo) const
{
Event replyToEvent = replyTo.isEmpty()
? Event()
: m_room.message(lager::make_constant(replyTo.toStdString())).make().get();
auto j = makeTextMessageJson(text, replyTo, replyToEvent);
m_room.sendMessage(Event(j));
}
void MatrixRoom::sendMediaFileMessage(QString fileName, QString mimeType, qint64 fileSize, QString mxcUri) const
{
auto j = makeMediaFileMessageJson(fileName, mimeType, fileSize, mxcUri);
Kazv::Event e{j};
m_room.sendMessage(e);
}
void MatrixRoom::sendEncryptedFileMessage(const QString &fileName, const QString &mimeType,
const qint64 fileSize, const QString &mxcUri,
const QString &key, const QString &iv, const QByteArray &hash) const
{
auto j = makeEncryptedFileMessageJson(fileName, mimeType, fileSize, mxcUri, key, iv, hash);
Kazv::Event e{j};
m_room.sendMessage(e);
}
static nlohmann::json makeReactionJson(QString text, QString relatedTo)
{
auto msg = nlohmann::json{
{"type", "m.reaction"},
{"content", {
{"m.relates_to", {
{"rel_type", "m.annotation"},
{"event_id", relatedTo.toStdString()},
{"key", text.toStdString()},
}},
}},
};
return msg;
}
void MatrixRoom::sendReaction(QString text, QString relatedTo) const
{
auto j = makeReactionJson(text, relatedTo);
m_room.sendMessage(Event(j));
}
void MatrixRoom::resendMessage(QString txnId) const
{
m_room.resendMessage(txnId.toStdString());
}
nlohmann::json MatrixRoom::makeMediaFileMessageJson(QString fileName, QString mimeType, qint64 fileSize, QString mxcUri) const
{
static auto available_msgtype = std::array<std::string, 3>{"m.audio", "m.video", "m.image"};
auto try_msgtype = std::find(available_msgtype.begin(), available_msgtype.end(),
QStringLiteral("m.").append(mimeType.split(QChar('/'))[0]).toStdString());
std::string msgtype;
if (try_msgtype == available_msgtype.end()) {
msgtype = "m.file";
} else {
msgtype = *try_msgtype;
}
return nlohmann::json {
{"type", "m.room.message"},
{"content", {
{"msgtype", msgtype},
{"body", fileName.toStdString()},
{"url", mxcUri.toStdString()},
{"info", {
{"size", fileSize},
{"mimetype", mimeType.toStdString()}
}}
}}
};
}
nlohmann::json MatrixRoom::makeEncryptedFileMessageJson(const QString &fileName, const QString &mimeType,
const qint64 fileSize, const QString &mxcUri,
const QString &key, const QString &iv, const QByteArray &hash) const
{
static auto available_msgtype = std::array<std::string, 3>{"m.audio", "m.video", "m.image"};
auto try_msgtype = std::find(available_msgtype.begin(), available_msgtype.end(),
QStringLiteral("m.").append(mimeType.split(QChar('/'))[0]).toStdString());
std::string msgtype;
if (try_msgtype == available_msgtype.end()) {
msgtype = "m.file";
} else {
msgtype = *try_msgtype;
}
return nlohmann::json {
{"type", "m.room.message"},
{"content", {
{"msgtype", msgtype},
{"body", fileName.toStdString()},
{"file", {
{"url", mxcUri.toStdString()},
{"key", {
{"kty", "oct"},
{"key_ops", nlohmann::json::array({"encrypt", "decrypt"})},
{"alg", "A256CTR"},
{"k", key.toStdString()},
{"ext", true}
}},
{"iv", iv.toStdString()},
{"hashes", {
{"sha256", hash.toStdString()}
}},
{"v", "v2"}
}},
{"info", {
{"size", fileSize},
{"mimetype", mimeType.toStdString()}
}}
}}
};
}
MatrixPromise *MatrixRoom::redactEvent(QString eventId, QString reason) const
{
qCInfo(kazvLog) << "redactEvent(" << eventId << ", " << reason << ")";
return new MatrixPromise(m_room.redactEvent(
eventId.toStdString(),
reason.isEmpty() ? std::nullopt : std::optional<std::string>(reason.toStdString())
));
}
MatrixPromise *MatrixRoom::removeLocalEcho(QString txnId) const
{
return new MatrixPromise(m_room.removeLocalEcho(txnId.toStdString()));
}
MatrixPromise *MatrixRoom::addOrSetTag(QString tagId) const
{
return new MatrixPromise(m_room.addOrSetTag(tagId.toStdString()));
}
MatrixPromise *MatrixRoom::removeTag(QString tagId) const
{
return new MatrixPromise(m_room.removeTag(tagId.toStdString()));
}
MatrixPromise *MatrixRoom::paginateBackFrom(QString eventId) const
{
return new MatrixPromise(m_room.paginateBackFromEvent(eventId.toStdString()));
}
MatrixPromise *MatrixRoom::leaveRoom() const
{
return new MatrixPromise(m_room.leave());
}
MatrixRoomMember *MatrixRoom::memberAt(int index) const
{
return new MatrixRoomMember(m_room.memberEventByCursor(
m_memberNames[index][lager::lenses::or_default]));
}
MatrixRoomMember *MatrixRoom::member(QString userId) const
{
return new MatrixRoomMember(m_room.memberEventFor(userId.toStdString()));
}
MatrixEvent *MatrixRoom::messageById(QString eventId) const
{
return new MatrixEvent(
m_room.message(lager::make_constant(eventId.toStdString()))
- .map([](const auto &e) -> std::variant<Kazv::Event, Kazv::LocalEchoDesc> { return e; })
+ .map([](const auto &e) -> std::variant<Kazv::Event, Kazv::LocalEchoDesc> { return e; }),
+ m_room
);
}
MatrixRoomMemberListModel *MatrixRoom::typingUsers() const
{
return new MatrixRoomMemberListModel(
m_room.typingMemberEvents()
.xform(containerMap(EventList{}, zug::filter([self=m_selfUserId.make().get()](const auto &e) {
return e.stateKey() != self;
})))
);
}
void MatrixRoom::setTyping(bool typing)
{
if (typing) {
m_setTypingThrottled();
} else {
m_room.setTyping(false, std::nullopt);
}
}
void MatrixRoom::setTypingImpl()
{
m_room.setTyping(true, typingTimeoutMs);
}
void MatrixRoom::setLocalDraft(QString localDraft)
{
// To avoid heavy computations when updating the local draft again
// and again, we only set our internal state. **Assume only
// one MatrixRoom can deal with the local draft** at the same time,
// it is safe to update it debounced and when we are destructed.
// See also the destructor.
m_internalLocalDraft = localDraft;
m_updateLocalDraftDebounced();
}
void MatrixRoom::updateLocalDraftNow()
{
if (m_internalLocalDraft.has_value()) {
m_room.setLocalDraft(m_internalLocalDraft.value().toStdString());
m_internalLocalDraft = std::nullopt;
}
}
MatrixRoomMemberListModel *MatrixRoom::members() const
{
return new MatrixRoomMemberListModel(
m_room.joinedMemberEvents()
);
}
MatrixRoomMemberListModel *MatrixRoom::bannedMembers() const
{
return new MatrixRoomMemberListModel(
m_room.bannedMemberEvents()
);
}
qint64 MatrixRoom::userPowerLevel(const QString &userId) const
{
auto e = m_powerLevels.get().normalizedEvent().content().get();
auto userIdStd = userId.toStdString();
return e.at("users").contains(userIdStd)
? e.at("users")[userIdStd].template get<Kazv::PowerLevel>()
: e.at("users_default").template get<Kazv::PowerLevel>();
}
MatrixPromise *MatrixRoom::setUserPowerLevel(const QString &userId, qint64 powerLevel) const
{
auto next = m_powerLevels.get().setUser(userId.toStdString(), powerLevel);
return new MatrixPromise(m_room.sendStateEvent(next.originalEvent()));
}
MatrixPromise *MatrixRoom::unsetUserPowerLevel(const QString &userId) const
{
auto next = m_powerLevels.get().setUser(userId.toStdString(), std::nullopt);
return new MatrixPromise(m_room.sendStateEvent(next.originalEvent()));
}
MatrixPromise *MatrixRoom::getStateEvent(const QString &type, const QString &stateKey) const
{
return new MatrixPromise(m_room.getStateEvent(
type.toStdString(), stateKey.toStdString()
));
}
MatrixPromise *MatrixRoom::ensureStateEvent(const QString &type, const QString &stateKey) const
{
if (m_room
.stateOpt({type.toStdString(), stateKey.toStdString()})
.make()
.get()
.has_value()) {
return MatrixPromise::createResolved(true, QJsonObject());
} else {
return getStateEvent(type, stateKey);
}
}
MatrixPromise *MatrixRoom::kickUser(const QString &userId, const QString &reason) const
{
return new MatrixPromise(m_room.kick(userId.toStdString(), reason.toStdString()));
}
MatrixPromise *MatrixRoom::banUser(const QString &userId, const QString &reason) const
{
return new MatrixPromise(m_room.ban(userId.toStdString(), reason.toStdString()));
}
MatrixPromise *MatrixRoom::unbanUser(const QString &userId) const
{
return new MatrixPromise(m_room.unban(userId.toStdString()));
}
MatrixPromise *MatrixRoom::inviteUser(const QString &userId) const
{
return new MatrixPromise(m_room.invite(userId.toStdString()));
}
diff --git a/src/tests/matrix-room-timeline-test.cpp b/src/tests/matrix-room-timeline-test.cpp
index cd11449..c77fc59 100644
--- a/src/tests/matrix-room-timeline-test.cpp
+++ b/src/tests/matrix-room-timeline-test.cpp
@@ -1,56 +1,96 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include <immer/config.hpp> // https://github.com/arximboldi/immer/issues/168
#include <memory>
#include <QtTest>
+#include <factory.hpp>
+
#include <matrix-room-timeline.hpp>
#include <matrix-sdk.hpp>
#include <matrix-room-list.hpp>
#include <matrix-room.hpp>
#include <matrix-event.hpp>
+#include <matrix-event-reader-list-model.hpp>
+#include <matrix-event-reader.hpp>
#include "test-model.hpp"
#include "test-utils.hpp"
using namespace Kazv;
+using namespace Kazv::Factory;
class MatrixRoomTimelineTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testLocalEcho();
+ void testReadReceipts();
};
void MatrixRoomTimelineTest::testLocalEcho()
{
auto model = makeTestModel();
std::unique_ptr<MatrixSdk> sdk{makeTestSdk(model)};
auto roomList = toUniquePtr(sdk->roomList());
auto room = toUniquePtr(roomList->room("!foo:tusooa.xyz"));
auto timeline = toUniquePtr(room->timeline());
QCOMPARE(timeline->count(), 2);
auto first = toUniquePtr(timeline->at(1)); // reverse order
QCOMPARE(first->isSending(), true);
QCOMPARE(first->isLocalEcho(), true);
QCOMPARE(first->isFailed(), false);
QCOMPARE(first->txnId(), "some-txn-id");
auto second = toUniquePtr(timeline->at(0));
QCOMPARE(second->isSending(), false);
QCOMPARE(second->isLocalEcho(), true);
QCOMPARE(second->isFailed(), true);
QCOMPARE(second->txnId(), "some-other-txn-id");
}
+void MatrixRoomTimelineTest::testReadReceipts()
+{
+ auto r = makeRoom(withRoomTimeline({
+ makeEvent(withEventId("$1")),
+ makeEvent(withEventId("$2")),
+ }));
+ r.eventReadUsers = {{"$1", {"@a:example.com", "@b:example.com"}}};
+ r.readReceipts = {{"@a:example.com", {"$1", 1234}}, {"@b:example.com", {"$2", 5678}}};
+ auto model = SdkModel{makeClient(withRoom(r))};
+ std::unique_ptr<MatrixSdk> sdk{makeTestSdk(model)};
+ auto roomList = toUniquePtr(sdk->roomList());
+ auto room = toUniquePtr(roomList->room(QString::fromStdString(r.roomId)));
+ auto timeline = toUniquePtr(room->timeline());
+ {
+ auto event = toUniquePtr(timeline->at(1));
+ QCOMPARE(event->eventId(), "$1");
+ auto readers = toUniquePtr(event->readers());
+ QCOMPARE(readers->count(), 2);
+ auto reader1 = toUniquePtr(readers->at(0));
+ QCOMPARE(reader1->userId(), "@a:example.com");
+ QCOMPARE(reader1->timestamp(), 1234);
+ auto reader2 = toUniquePtr(readers->at(1));
+ QCOMPARE(reader2->userId(), "@b:example.com");
+ QCOMPARE(reader2->timestamp(), 5678);
+ }
+
+ {
+ auto event = toUniquePtr(timeline->at(0));
+ QCOMPARE(event->eventId(), "$2");
+ auto readers = toUniquePtr(event->readers());
+ QCOMPARE(readers->count(), 0);
+ }
+}
+
QTEST_MAIN(MatrixRoomTimelineTest)
#include "matrix-room-timeline-test.moc"

File Metadata

Mime Type
text/x-diff
Expires
Mon, Nov 25, 5:42 AM (1 d, 7 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39636
Default Alt Text
(36 KB)

Event Timeline