Page MenuHomePhorge

No OneTemporary

Size
39 KB
Referenced Files
None
Subscribers
None
diff --git a/src/matrix-event-reader-list-model.cpp b/src/matrix-event-reader-list-model.cpp
index f10341f..2022476 100644
--- a/src/matrix-event-reader-list-model.cpp
+++ b/src/matrix-event-reader-list-model.cpp
@@ -1,38 +1,39 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2024 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <kazv-defs.hpp>
#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);
}))),
QString(),
+ lager::make_constant(Kazv::Event()),
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-room-list.cpp b/src/matrix-room-list.cpp
index aa45aa6..e6d4b0c 100644
--- a/src/matrix-room-list.cpp
+++ b/src/matrix-room-list.cpp
@@ -1,221 +1,225 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2024 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <kazv-defs.hpp>
#include <zug/into_vector.hpp>
#include <zug/transducer/cat.hpp>
#include <lager/commit.hpp>
#include <lager/constant.hpp>
#include <lager/lenses/optional.hpp>
#include "matrix-room-list.hpp"
#include "matrix-room.hpp"
#include "matrix-user-given-attrs-map.hpp"
#include "matrix-utils.hpp"
#include "helper.hpp"
using namespace Kazv;
static Timestamp latestEventTimestamp(const RoomModel &room)
{
if (room.timeline.empty()) {
return 0;
}
auto latestEventId = room.timeline[room.timeline.size() - 1];
auto latestEvent = room.messages[latestEventId];
return latestEvent.originServerTs();
}
MatrixRoomList::MatrixRoomList(Kazv::Client client, QString tagId, QString filter, QObject *parent)
: QAbstractListModel(parent)
, m_client(client)
, m_tagId(tagId)
, m_tagIdCursor(lager::make_sensor([this] { return m_tagId.toStdString(); }))
, m_internalCount(0)
, m_filter(lager::make_state(filter.toStdString(), lager::automatic_tag{}))
, m_userGivenNicknameMap(userGivenNicknameMapFor(m_client).map(&Event::content))
, m_roomIds(lager::with(m_tagIdCursor, m_client.rooms(), m_filter, m_client.roomIdsByTagId(), m_userGivenNicknameMap)
.map([](const auto &tagIdStdStr, const auto &allRooms, const auto &filter, const auto &roomsByTagMap, const auto &userGivenNicknameMap) {
auto toId = zug::map([](const auto &pair) {
return pair.first;
});
auto roomName = [](const auto &room) {
auto content = room.stateEvents[{"m.room.name", ""}].content().get();
if (content.contains("name") && content["name"].is_string()) {
return content["name"].template get<std::string>();
}
return std::string();
};
auto roomHeroNames = [m=userGivenNicknameMap](const auto &room) {
auto heroEvents = room.heroMemberEvents();
return intoImmer(
immer::flex_vector<std::string>(),
zug::map([m](const Event &ev) {
auto userId = ev.stateKey();
auto content = ev.content().get();
auto res = std::vector<std::string>{};
if (content.contains("displayname") && content["displayname"].is_string()) {
res.push_back(content["displayname"].template get<std::string>());
}
if (m.get().contains(userId)
&& m.get()[userId].is_string()) {
res.push_back(m.get()[userId].template get<std::string>());
}
return res;
})
| zug::cat,
heroEvents
);
};
auto applyFilter = zug::filter([&filter, &allRooms, &roomName, &roomHeroNames](const auto &id) {
if (filter.empty()) {
return true;
}
const auto &room = allRooms[id];
// Use exact match for room id
if (room.roomId == filter) {
return true;
}
auto name = roomName(room);
if (!name.empty()) {
// Use substring match for name search
return name.find(filter) != std::string::npos;
}
// The room has no name, use hero names for the search
auto heroes = roomHeroNames(room);
// If any of the room hero matches the filter, consider it a match
return std::any_of(heroes.begin(), heroes.end(),
[&filter](const auto &name) {
return name.find(filter) != std::string::npos;
})
|| std::any_of(room.heroIds.begin(), room.heroIds.end(),
[&filter](const auto &id) {
return id.find(filter) != std::string::npos;
});
});
auto sortByTimestampDesc = [allRooms](std::vector<std::string> container) {
std::sort(
container.begin(),
container.end(),
[allRooms](const std::string &idA, const std::string &idB) {
const auto &roomA = allRooms[idA];
const auto &roomB = allRooms[idB];
auto aIsInvite = roomA.membership == Invite;
auto bIsInvite = roomB.membership == Invite;
if (aIsInvite != bIsInvite) {
/**
* if my membership in A is invite,
* then the membership in B is not invite,
* so A should come first;
* otherwise B should come first
**/
return aIsInvite;
} else {
return latestEventTimestamp(roomA)
> latestEventTimestamp(roomB);
}
}
);
return immer::flex_vector<std::string>(container.begin(), container.end());
};
if (tagIdStdStr.empty()) {
return sortByTimestampDesc(zug::into_vector(
toId | applyFilter,
allRooms
));
} else {
return sortByTimestampDesc(zug::into_vector(
toId | applyFilter,
roomsByTagMap[tagIdStdStr]
));
}
}))
, LAGER_QT(filter)(m_filter.xform(strToQt, qStringToStd))
, LAGER_QT(count)(m_roomIds.xform(containerSize))
, LAGER_QT(roomIds)(m_roomIds.xform(zug::map(
[](auto container) {
return zug::into(QStringList{}, strToQt, std::move(container));
})))
{
m_internalCount = count();
connect(this, &MatrixRoomList::countChanged, this, &MatrixRoomList::updateInternalCount);
}
MatrixRoomList::~MatrixRoomList() = default;
void MatrixRoomList::setTagId(QString tagId)
{
m_tagId = tagId;
lager::commit(m_tagIdCursor);
}
MatrixRoom *MatrixRoomList::at(int index) const
{
qDebug() << "Room at index " << index << " requested";
return new MatrixRoom(
m_client.roomByCursor(
lager::with(m_roomIds, lager::make_constant(index))
.xform(zug::map([](auto ids, auto i) {
try {
return ids.at(i);
} catch (const std::out_of_range &) {
return std::string{};
}
}))),
m_client.userId());
}
QString MatrixRoomList::roomIdAt(int index) const
{
using namespace Kazv::CursorOp;
return +m_roomIds[index][lager::lenses::or_default].xform(strToQt);
}
MatrixRoom *MatrixRoomList::room(QString roomId) const
{
- return new MatrixRoom(m_client.room(roomId.toStdString()), m_client.userId());
+ return new MatrixRoom(
+ m_client.room(roomId.toStdString()),
+ m_client.userId(),
+ userGivenNicknameMapFor(m_client)
+ );
}
QVariant MatrixRoomList::data(const QModelIndex &/* index */, int /* role */) const
{
return QVariant();
}
int MatrixRoomList::rowCount(const QModelIndex &parent = QModelIndex()) const
{
if (parent.isValid()) {
return 0;
} else {
return count();
}
}
void MatrixRoomList::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-member-list-model.cpp b/src/matrix-room-member-list-model.cpp
index 2f3ab83..724c255 100644
--- a/src/matrix-room-member-list-model.cpp
+++ b/src/matrix-room-member-list-model.cpp
@@ -1,88 +1,99 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <kazv-defs.hpp>
#include <cmath>
#include <lager/lenses/optional.hpp>
#include <cursorutil.hpp>
#include "helper.hpp"
#include "matrix-room-member-list-model.hpp"
#include "matrix-room-member.hpp"
using namespace Kazv;
-MatrixRoomMemberListModel::MatrixRoomMemberListModel(lager::reader<Kazv::EventList> members, QString filter, QObject *parent)
+MatrixRoomMemberListModel::MatrixRoomMemberListModel(lager::reader<Kazv::EventList> members, QString filter, lager::reader<Kazv::Event> userGivenNicknameEvent, QObject *parent)
: QAbstractListModel(parent)
, m_filter(lager::make_state(filter.toStdString(), lager::automatic_tag{}))
- , m_members(lager::with(members, m_filter).map([](const auto &members, const auto &filter) {
+ , m_members(lager::with(
+ members,
+ m_filter,
+ userGivenNicknameEvent.map(&Event::content)
+ ).map([](const auto &members, const auto &filter, const auto &map) {
if (filter.empty()) {
return members;
}
- return intoImmer(EventList{}, zug::filter([filter](const auto &e) {
+ return intoImmer(EventList{}, zug::filter([filter, map=map.get()](const auto &e) {
auto userId = e.stateKey();
if (userId.find(filter) != std::string::npos) {
return true;
}
+ // user given nickname match
auto content = e.content().get();
+ if (map.contains(userId) && map[userId].is_string()) {
+ auto name = map[userId].template get<std::string>();
+ if (name.find(filter) != std::string::npos) {
+ return true;
+ }
+ }
if (content.contains("displayname")
&& content["displayname"].is_string()) {
auto name = content["displayname"].template get<std::string>();
return name.find(filter) != std::string::npos;
}
return false;
}), members);
}))
, m_internalCount(0)
, LAGER_QT(filter)(m_filter.xform(strToQt, qStringToStd))
, LAGER_QT(count)(m_members.map([](const auto &m) -> int { return m.size(); }))
{
m_internalCount = count();
connect(this, &MatrixRoomMemberListModel::countChanged,
this, &MatrixRoomMemberListModel::updateInternalCount);
}
MatrixRoomMemberListModel::~MatrixRoomMemberListModel() = default;
MatrixRoomMember *MatrixRoomMemberListModel::at(int index) const
{
return new MatrixRoomMember(m_members[index][lager::lenses::or_default]);
}
int MatrixRoomMemberListModel::rowCount(const QModelIndex &index) const
{
if (index.isValid()) {
return 0;
} else {
return count();
}
}
QVariant MatrixRoomMemberListModel::data(const QModelIndex &, int) const
{
return QVariant();
}
void MatrixRoomMemberListModel::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-member-list-model.hpp b/src/matrix-room-member-list-model.hpp
index 869f437..0d38d57 100644
--- a/src/matrix-room-member-list-model.hpp
+++ b/src/matrix-room-member-list-model.hpp
@@ -1,44 +1,44 @@
/*
* 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 <kazv-defs.hpp>
#include <QObject>
#include <QQmlEngine>
#include <QAbstractListModel>
#include <lager/extra/qt.hpp>
#include <lager/state.hpp>
#include <client/room/room.hpp>
class MatrixRoomMember;
class MatrixRoomMemberListModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
lager::state<std::string, lager::automatic_tag> m_filter;
lager::reader<Kazv::EventList> m_members;
int m_internalCount;
public:
- explicit MatrixRoomMemberListModel(lager::reader<Kazv::EventList> members, QString filter = QString(), QObject *parent = 0);
+ explicit MatrixRoomMemberListModel(lager::reader<Kazv::EventList> members, QString filter = QString(), lager::reader<Kazv::Event> userGivenNicknameEvent = lager::make_constant(Kazv::Event()), QObject *parent = 0);
~MatrixRoomMemberListModel() override;
LAGER_QT_CURSOR(QString, filter);
LAGER_QT_READER(int, count);
int rowCount(const QModelIndex &index) const override;
QVariant data(const QModelIndex &index, int role) const override;
Q_INVOKABLE MatrixRoomMember *at(int index) const;
private Q_SLOTS:
void updateInternalCount();
};
diff --git a/src/matrix-room.cpp b/src/matrix-room.cpp
index 43a9ae9..03c7867 100644
--- a/src/matrix-room.cpp
+++ b/src/matrix-room.cpp
@@ -1,490 +1,495 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2024 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <kazv-defs.hpp>
#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 "qt-json.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)
+MatrixRoom::MatrixRoom(Kazv::Room room, lager::reader<std::string> selfUserId, lager::reader<Kazv::Event> userGivenNicknameEvent, QObject *parent)
: QObject(parent)
, m_room(room)
+ , m_userGivenNicknameEvent(userGivenNicknameEvent)
, 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(heroEvents)(m_room.heroMemberEvents()
.map([](const auto &events) {
auto res = QList<QVariant>{};
res.reserve(events.size());
std::transform(events.begin(), events.end(),
std::back_inserter(res),
[](const auto &event) -> QVariant {
return event.originalJson().get().template get<QJsonObject>();
});
return QVariant(res);
}))
, 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 &relType, const QString &relatedTo)
{
if (relatedTo.isEmpty()) {
return;
}
if (relType == "m.in_reply_to") {
msg["content"]["m.relates_to"] = nlohmann::json{
{"m.in_reply_to", {
{"event_id", relatedTo},
}},
};
} else {
msg["content"]["m.relates_to"] = nlohmann::json{
{"rel_type", relType},
{"event_id", relatedTo},
};
}
}
static const std::string HTML_FORMAT = "org.matrix.custom.html";
nlohmann::json makeTextMessageJson(const QString &text, const QString &relType, const QString &relatedTo, Event replyToEvent)
{
auto msg = nlohmann::json{
{"type", "m.room.message"},
{"content", {
{"msgtype", "m.text"},
{"body", text},
}},
};
std::string replyToBody;
if (relType == "m.in_reply_to" && !relatedTo.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},
};
}
if (relType == "m.replace" && !relatedTo.isEmpty()) {
msg["content"]["m.new_content"] = msg["content"];
}
maybeAddRelations(msg, relType, relatedTo);
return msg;
}
void MatrixRoom::sendMessage(const QJsonObject &eventJson, const QString &relType, const QString &relatedTo) const
{
auto msg = nlohmann::json(eventJson);
maybeAddRelations(msg, relType, relatedTo);
m_room.sendMessage(Event(msg));
}
void MatrixRoom::sendTextMessage(QString text, const QString &relType, QString relatedTo) const
{
Event replyToEvent = (relType == "m.in_reply_to" && !relatedTo.isEmpty())
? m_room.message(lager::make_constant(relatedTo.toStdString())).make().get()
: Event();
auto j = makeTextMessageJson(text, relType, relatedTo, 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());
}
MatrixPromise *MatrixRoom::sendStateEvent(const QJsonObject &eventJson) const
{
return new MatrixPromise(m_room.sendStateEvent(Event(json(eventJson))));
}
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; }),
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()
+ m_room.joinedMemberEvents(),
+ QString(),
+ m_userGivenNicknameEvent
);
}
MatrixRoomMemberListModel *MatrixRoom::bannedMembers() const
{
return new MatrixRoomMemberListModel(
- m_room.bannedMemberEvents()
+ m_room.bannedMemberEvents(),
+ QString(),
+ m_userGivenNicknameEvent
);
}
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::refreshState() const
{
return new MatrixPromise(m_room.refreshRoomState());
}
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()));
}
MatrixPromise *MatrixRoom::postReadReceipt(const QString &eventId) const
{
return new MatrixPromise(m_room.postReceipt(eventId.toStdString()));
}
diff --git a/src/matrix-room.hpp b/src/matrix-room.hpp
index 9655363..701e67a 100644
--- a/src/matrix-room.hpp
+++ b/src/matrix-room.hpp
@@ -1,150 +1,151 @@
/*
* 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 <kazv-defs.hpp>
#include <QObject>
#include <QQmlEngine>
#include <QUrl>
#include <QTimer>
#include <lager/extra/qt.hpp>
#include <client/room/room.hpp>
class MatrixRoomTimeline;
class MatrixRoomMember;
class MatrixPromise;
class MatrixRoomMemberListModel;
class MatrixEvent;
nlohmann::json makeTextMessageJson(const QString &text, const QString &relType, const QString &relatedTo, Kazv::Event replyToEvent);
class MatrixRoom : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
Kazv::Room m_room;
+ lager::reader<Kazv::Event> m_userGivenNicknameEvent;
lager::reader<std::string> m_selfUserId;
lager::reader<immer::flex_vector<std::string>> m_memberNames;
lager::reader<Kazv::PowerLevelsDesc> m_powerLevels;
public:
enum Membership {
Invite = Kazv::RoomMembership::Invite,
Join = Kazv::RoomMembership::Join,
Leave = Kazv::RoomMembership::Leave,
};
Q_ENUM(Membership);
- explicit MatrixRoom(Kazv::Room room, lager::reader<std::string> selfUserId, QObject *parent = 0);
+ explicit MatrixRoom(Kazv::Room room, lager::reader<std::string> selfUserId, lager::reader<Kazv::Event> userGivenNicknameEvent = lager::make_constant(Kazv::Event()), QObject *parent = 0);
~MatrixRoom() override;
LAGER_QT_READER(QString, roomId);
LAGER_QT_READER(QString, name);
LAGER_QT_READER(QStringList, heroNames);
LAGER_QT_READER(QVariant, heroEvents);
LAGER_QT_READER(QString, avatarMxcUri);
LAGER_QT_READER(QString, roomOrHeroAvatarMxcUri);
LAGER_QT_READER(QString, localDraft);
LAGER_QT_READER(bool, encrypted);
LAGER_QT_READER(QStringList, memberNames);
LAGER_QT_READER(QStringList, tagIds);
LAGER_QT_READER(Membership, membership);
Q_INVOKABLE MatrixRoomMember *memberAt(int index) const;
Q_INVOKABLE MatrixRoomMember *member(QString userId) const;
Q_INVOKABLE MatrixRoomTimeline *timeline() const;
Q_INVOKABLE MatrixEvent *messageById(QString eventId) const;
Q_INVOKABLE void sendMessage(const QJsonObject &eventJson, const QString &relType, const QString &relatedTo) const;
Q_INVOKABLE void sendTextMessage(QString text, const QString &relType, QString relatedTo) const;
Q_INVOKABLE void sendMediaFileMessage(QString fileName, QString mimeType,
qint64 fileSize, QString mxcUri) const;
Q_INVOKABLE void sendEncryptedFileMessage(const QString &fileName, const QString& mimeType,
const qint64 fileSize, const QString& mxcUri,
const QString &key, const QString &iv, const QByteArray &hash) const;
Q_INVOKABLE void sendReaction(QString text, QString relatedTo) const;
Q_INVOKABLE void resendMessage(QString txnId) const;
Q_INVOKABLE MatrixPromise *sendStateEvent(const QJsonObject &eventJson) const;
Q_INVOKABLE MatrixPromise *redactEvent(QString eventId, QString reason) const;
Q_INVOKABLE MatrixPromise *removeLocalEcho(QString txnId) const;
Q_INVOKABLE MatrixRoomMemberListModel *typingUsers() const;
Q_INVOKABLE void setTyping(bool typing);
Q_INVOKABLE void setLocalDraft(QString localDraft);
Q_INVOKABLE void updateLocalDraftNow();
Q_INVOKABLE MatrixPromise *addOrSetTag(QString tagId) const;
Q_INVOKABLE MatrixPromise *removeTag(QString tagId) const;
Q_INVOKABLE MatrixPromise *paginateBackFrom(QString eventId) const;
Q_INVOKABLE MatrixPromise *leaveRoom() const;
Q_INVOKABLE MatrixRoomMemberListModel *members() const;
Q_INVOKABLE MatrixRoomMemberListModel *bannedMembers() const;
Q_INVOKABLE qint64 userPowerLevel(const QString &userId) const;
Q_INVOKABLE MatrixPromise *setUserPowerLevel(const QString &userId, qint64 powerLevel) const;
Q_INVOKABLE MatrixPromise *unsetUserPowerLevel(const QString &userId) const;
Q_INVOKABLE MatrixPromise *getStateEvent(const QString &type, const QString &stateKey) const;
Q_INVOKABLE MatrixPromise *ensureStateEvent(const QString &type, const QString &stateKey) const;
Q_INVOKABLE MatrixPromise *refreshState() const;
Q_INVOKABLE MatrixPromise *kickUser(const QString &userId, const QString &reason) const;
Q_INVOKABLE MatrixPromise *banUser(const QString &userId, const QString &reason = "") const;
Q_INVOKABLE MatrixPromise *unbanUser(const QString &userId) const;
Q_INVOKABLE MatrixPromise *inviteUser(const QString &userId) const;
Q_INVOKABLE MatrixPromise *postReadReceipt(const QString &eventId) const;
Q_SIGNALS:
void powerLevelsChanged();
protected:
nlohmann::json makeMediaFileMessageJson(QString fileName, QString mimeType,
qint64 fileSize, QString mxcUri) const;
nlohmann::json makeEncryptedFileMessageJson(const QString &fileName, const QString &mimeType,
const qint64 fileSize, const QString &mxcUri,
const QString &key, const QString &iv, const QByteArray &hash) const;
private Q_SLOTS:
void setTypingImpl();
private:
std::function<void()> m_setTypingThrottled;
std::function<void()> m_updateLocalDraftDebounced;
std::optional<QString> m_internalLocalDraft;
};
diff --git a/src/tests/matrix-room-member-list-model-test.cpp b/src/tests/matrix-room-member-list-model-test.cpp
index e6dc209..1ae702b 100644
--- a/src/tests/matrix-room-member-list-model-test.cpp
+++ b/src/tests/matrix-room-member-list-model-test.cpp
@@ -1,78 +1,91 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <kazv-defs.hpp>
#include <memory>
#include <QtTest>
#include <lager/constant.hpp>
#include <matrix-room-member-list-model.hpp>
#include <matrix-room-member.hpp>
#include "test-model.hpp"
#include "test-utils.hpp"
using namespace Kazv;
class MatrixRoomMemberListModelTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testMembers();
void testFilter();
};
static auto data = Kazv::EventList{
makeRoomMember("@t1:tusooa.xyz", "user 1"),
makeRoomMember("@t2:tusooa.xyz"),
};
void MatrixRoomMemberListModelTest::testMembers()
{
MatrixRoomMemberListModel model(lager::make_constant(data));
QCOMPARE(model.count(), 2);
auto first = toUniquePtr(model.at(0));
QCOMPARE(first->name(), "user 1");
QCOMPARE(first->userId(), "@t1:tusooa.xyz");
auto second = toUniquePtr(model.at(1));
QCOMPARE(second->name(), QStringLiteral());
QCOMPARE(second->userId(), "@t2:tusooa.xyz");
}
void MatrixRoomMemberListModelTest::testFilter()
{
- MatrixRoomMemberListModel model(lager::make_constant(data));
+ MatrixRoomMemberListModel model(
+ lager::make_constant(data),
+ QString(),
+ lager::make_constant(Event(json{
+ {"content", {{"@t1:tusooa.xyz", "mewmew"}}},
+ }))
+ );
QCOMPARE(model.count(), 2);
model.setfilter(QString("@t"));
QCOMPARE(model.count(), 2);
{
model.setfilter(QString("user 1"));
QCOMPARE(model.count(), 1);
auto first = toUniquePtr(model.at(0));
QCOMPARE(first->userId(), QString("@t1:tusooa.xyz"));
}
{
model.setfilter(QString("t2"));
QCOMPARE(model.count(), 1);
auto first = toUniquePtr(model.at(0));
QCOMPARE(first->userId(), QString("@t2:tusooa.xyz"));
}
+
+ {
+ model.setfilter(QString("mewmew"));
+ QCOMPARE(model.count(), 1);
+ auto first = toUniquePtr(model.at(0));
+ QCOMPARE(first->userId(), QString("@t1:tusooa.xyz"));
+ }
}
QTEST_MAIN(MatrixRoomMemberListModelTest)
#include "matrix-room-member-list-model-test.moc"

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 12:01 PM (4 h, 42 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
55147
Default Alt Text
(39 KB)

Event Timeline