Newer
Older
#include <QRegularExpression>
#include "MainWindow.h"
eventId(const mtx::events::RoomEvent<T> &event)
{
return QString::fromStdString(event.event_id);
}
template<class T>
QString
roomId(const mtx::events::Event<T> &event)
{
return QString::fromStdString(event.room_id);
}
template<class T>
QString
senderId(const mtx::events::RoomEvent<T> &event)
{
return QString::fromStdString(event.sender);
}
template<class T>
QDateTime
eventTimestamp(const mtx::events::RoomEvent<T> &event)
{
return QDateTime::fromMSecsSinceEpoch(event.origin_server_ts);
}
template<class T>
std::string
eventMsgType(const mtx::events::Event<T> &)
{
return "";
}
template<class T>
auto
eventMsgType(const mtx::events::RoomEvent<T> &e) -> decltype(e.content.msgtype)
{
return e.content.msgtype;
}
template<class T>
QString
eventBody(const mtx::events::Event<T> &)
{
return QString("");
}
template<class T>
auto
eventBody(const mtx::events::RoomEvent<T> &e)
-> std::enable_if_t<std::is_same<decltype(e.content.body), std::string>::value, QString>
{
return QString::fromStdString(e.content.body);
}
template<class T>
QString
eventFormattedBody(const mtx::events::Event<T> &)
{
return QString("");
}
template<class T>
auto
eventFormattedBody(const mtx::events::RoomEvent<T> &e)
-> std::enable_if_t<std::is_same<decltype(e.content.formatted_body), std::string>::value, QString>
{
auto temp = e.content.formatted_body;
if (!temp.empty()) {
return QString::fromStdString(temp);
} else {
return QString::fromStdString(e.content.body).toHtmlEscaped().replace("\n", "<br>");
}
template<class T>
QString
eventUrl(const mtx::events::Event<T> &)
{
return "";
}
template<class T>
auto
eventUrl(const mtx::events::RoomEvent<T> &e)
-> std::enable_if_t<std::is_same<decltype(e.content.url), std::string>::value, QString>
{
return QString::fromStdString(e.content.url);
}
template<class T>
QString
eventThumbnailUrl(const mtx::events::Event<T> &)
{
return "";
}
template<class T>
auto
eventThumbnailUrl(const mtx::events::RoomEvent<T> &e)
-> std::enable_if_t<std::is_same<decltype(e.content.info.thumbnail_url), std::string>::value,
QString>
{
return QString::fromStdString(e.content.info.thumbnail_url);
}
eventFilename(const mtx::events::Event<T> &)
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
{
return "";
}
QString
eventFilename(const mtx::events::RoomEvent<mtx::events::msg::Audio> &e)
{
// body may be the original filename
return QString::fromStdString(e.content.body);
}
QString
eventFilename(const mtx::events::RoomEvent<mtx::events::msg::Video> &e)
{
// body may be the original filename
return QString::fromStdString(e.content.body);
}
QString
eventFilename(const mtx::events::RoomEvent<mtx::events::msg::Image> &e)
{
// body may be the original filename
return QString::fromStdString(e.content.body);
}
QString
eventFilename(const mtx::events::RoomEvent<mtx::events::msg::File> &e)
{
// body may be the original filename
if (!e.content.filename.empty())
return QString::fromStdString(e.content.filename);
return QString::fromStdString(e.content.body);
}
template<class T>
auto
eventFilesize(const mtx::events::RoomEvent<T> &e) -> decltype(e.content.info.size)
{
return e.content.info.size;
}
template<class T>
int64_t
eventFilesize(const mtx::events::Event<T> &)
eventMimeType(const mtx::events::Event<T> &)
{
return QString();
}
template<class T>
auto
eventMimeType(const mtx::events::RoomEvent<T> &e)
-> std::enable_if_t<std::is_same<decltype(e.content.info.mimetype), std::string>::value, QString>
{
return QString::fromStdString(e.content.info.mimetype);
}
template<class T>
QString
eventRelatesTo(const mtx::events::Event<T> &)
{
return QString();
}
template<class T>
auto
eventRelatesTo(const mtx::events::RoomEvent<T> &e) -> std::enable_if_t<
std::is_same<decltype(e.content.relates_to.in_reply_to.event_id), std::string>::value,
QString>
{
return QString::fromStdString(e.content.relates_to.in_reply_to.event_id);
}
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
template<class T>
qml_mtx_events::EventType
toRoomEventType(const mtx::events::Event<T> &e)
{
using mtx::events::EventType;
switch (e.type) {
case EventType::RoomKeyRequest:
return qml_mtx_events::EventType::KeyRequest;
case EventType::RoomAliases:
return qml_mtx_events::EventType::Aliases;
case EventType::RoomAvatar:
return qml_mtx_events::EventType::Avatar;
case EventType::RoomCanonicalAlias:
return qml_mtx_events::EventType::CanonicalAlias;
case EventType::RoomCreate:
return qml_mtx_events::EventType::Create;
case EventType::RoomEncrypted:
return qml_mtx_events::EventType::Encrypted;
case EventType::RoomEncryption:
return qml_mtx_events::EventType::Encryption;
case EventType::RoomGuestAccess:
return qml_mtx_events::EventType::GuestAccess;
case EventType::RoomHistoryVisibility:
return qml_mtx_events::EventType::HistoryVisibility;
case EventType::RoomJoinRules:
return qml_mtx_events::EventType::JoinRules;
case EventType::RoomMember:
return qml_mtx_events::EventType::Member;
case EventType::RoomMessage:
return qml_mtx_events::EventType::UnknownMessage;
case EventType::RoomName:
return qml_mtx_events::EventType::Name;
case EventType::RoomPowerLevels:
return qml_mtx_events::EventType::PowerLevels;
case EventType::RoomTopic:
return qml_mtx_events::EventType::Topic;
case EventType::RoomTombstone:
return qml_mtx_events::EventType::Tombstone;
case EventType::RoomRedaction:
return qml_mtx_events::EventType::Redaction;
case EventType::RoomPinnedEvents:
return qml_mtx_events::EventType::PinnedEvents;
case EventType::Sticker:
return qml_mtx_events::EventType::Sticker;
case EventType::Tag:
return qml_mtx_events::EventType::Tag;
case EventType::Unsupported:
default:
return qml_mtx_events::EventType::Unsupported;
}
}
qml_mtx_events::EventType
toRoomEventType(const mtx::events::Event<mtx::events::msg::Audio> &)
{
return qml_mtx_events::EventType::AudioMessage;
}
qml_mtx_events::EventType
toRoomEventType(const mtx::events::Event<mtx::events::msg::Emote> &)
{
return qml_mtx_events::EventType::EmoteMessage;
}
qml_mtx_events::EventType
toRoomEventType(const mtx::events::Event<mtx::events::msg::File> &)
{
return qml_mtx_events::EventType::FileMessage;
}
qml_mtx_events::EventType
toRoomEventType(const mtx::events::Event<mtx::events::msg::Image> &)
{
return qml_mtx_events::EventType::ImageMessage;
}
qml_mtx_events::EventType
toRoomEventType(const mtx::events::Event<mtx::events::msg::Notice> &)
{
return qml_mtx_events::EventType::NoticeMessage;
}
qml_mtx_events::EventType
toRoomEventType(const mtx::events::Event<mtx::events::msg::Text> &)
{
return qml_mtx_events::EventType::TextMessage;
}
qml_mtx_events::EventType
toRoomEventType(const mtx::events::Event<mtx::events::msg::Video> &)
{
return qml_mtx_events::EventType::VideoMessage;
}
qml_mtx_events::EventType
toRoomEventType(const mtx::events::Event<mtx::events::msg::Redacted> &)
{
return qml_mtx_events::EventType::Redacted;
}
// ::EventType::Type toRoomEventType(const Event<mtx::events::msg::Location> &e) { return
// ::EventType::LocationMessage; }
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
template<class T>
uint64_t
eventHeight(const mtx::events::Event<T> &)
{
return -1;
}
template<class T>
auto
eventHeight(const mtx::events::RoomEvent<T> &e) -> decltype(e.content.info.h)
{
return e.content.info.h;
}
template<class T>
uint64_t
eventWidth(const mtx::events::Event<T> &)
{
return -1;
}
template<class T>
auto
eventWidth(const mtx::events::RoomEvent<T> &e) -> decltype(e.content.info.w)
{
return e.content.info.w;
}
template<class T>
double
eventPropHeight(const mtx::events::RoomEvent<T> &e)
{
auto w = eventWidth(e);
if (w == 0)
w = 1;
return eventHeight(e) / (double)w;
}
TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObject *parent)
: QAbstractListModel(parent)
, room_id_(room_id)
{
connect(
this, &TimelineModel::oldMessagesRetrieved, this, &TimelineModel::addBackwardsEvents);
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
connect(this, &TimelineModel::messageFailed, this, [this](QString txn_id) {
pending.remove(txn_id);
failed.insert(txn_id);
int idx = idToIndex(txn_id);
if (idx < 0) {
nhlog::ui()->warn("Failed index out of range");
return;
}
emit dataChanged(index(idx, 0), index(idx, 0));
});
connect(this, &TimelineModel::messageSent, this, [this](QString txn_id, QString event_id) {
int idx = idToIndex(txn_id);
if (idx < 0) {
nhlog::ui()->warn("Sent index out of range");
return;
}
eventOrder[idx] = event_id;
auto ev = events.value(txn_id);
ev = boost::apply_visitor(
[event_id](const auto &e) -> mtx::events::collections::TimelineEvents {
auto eventCopy = e;
eventCopy.event_id = event_id.toStdString();
return eventCopy;
},
ev);
events.remove(txn_id);
events.insert(event_id, ev);
// mark our messages as read
readEvent(event_id.toStdString());
// ask to be notified for read receipts
cache::client()->addPendingReceipt(room_id_, event_id);
emit dataChanged(index(idx, 0), index(idx, 0));
});
connect(this, &TimelineModel::redactionFailed, this, [](const QString &msg) {
emit ChatPage::instance()->showNotification(msg);
});
QHash<int, QByteArray>
TimelineModel::roleNames() const
{
return {
{Section, "section"},
{Type, "type"},
{Body, "body"},
{FormattedBody, "formattedBody"},
{UserId, "userId"},
{UserName, "userName"},
{Timestamp, "timestamp"},
{Height, "height"},
{Width, "width"},
{ProportionalHeight, "proportionalHeight"},
};
}
int
TimelineModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return (int)this->eventOrder.size();
}
QVariant
TimelineModel::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 && index.row() >= (int)eventOrder.size())
return QVariant();
QString id = eventOrder[index.row()];
mtx::events::collections::TimelineEvents event = events.value(id);
if (auto e = boost::get<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&event)) {
event = decryptEvent(*e).event;
}
case Section: {
QDateTime date = boost::apply_visitor(
[](const auto &e) -> QDateTime { return eventTimestamp(e); }, event);
date.setTime(QTime());
QString userId =
boost::apply_visitor([](const auto &e) -> QString { return senderId(e); }, event);
for (int r = index.row() - 1; r > 0; r--) {
QDateTime prevDate = boost::apply_visitor(
[](const auto &e) -> QDateTime { return eventTimestamp(e); },
events.value(eventOrder[r]));
prevDate.setTime(QTime());
if (prevDate != date)
return QString("%2 %1").arg(date.toMSecsSinceEpoch()).arg(userId);
QString prevUserId =
boost::apply_visitor([](const auto &e) -> QString { return senderId(e); },
events.value(eventOrder[r]));
if (userId != prevUserId)
break;
}
return QString("%1").arg(userId);
}
[](const auto &e) -> QString { return senderId(e); }, event));
case UserName:
return QVariant(displayName(boost::apply_visitor(
[](const auto &e) -> QString { return senderId(e); }, event)));
case Timestamp:
return QVariant(boost::apply_visitor(
[](const auto &e) -> QDateTime { return eventTimestamp(e); }, event));
case Type:
return QVariant(boost::apply_visitor(
[](const auto &e) -> qml_mtx_events::EventType { return toRoomEventType(e); },
case Body:
return QVariant(utils::replaceEmoji(boost::apply_visitor(
[](const auto &e) -> QString { return eventBody(e); }, event)));
return QVariant(
utils::replaceEmoji(
boost::apply_visitor(
[](const auto &e) -> QString { return eventFormattedBody(e); }, event))
.remove("<mx-reply>")
.remove("</mx-reply>"));
case Url:
return QVariant(boost::apply_visitor(
[](const auto &e) -> QString { return eventUrl(e); }, event));
case ThumbnailUrl:
return QVariant(boost::apply_visitor(
[](const auto &e) -> QString { return eventThumbnailUrl(e); }, event));
case Filename:
return QVariant(boost::apply_visitor(
[](const auto &e) -> QString { return eventFilename(e); }, event));
case Filesize:
return QVariant(boost::apply_visitor(
[](const auto &e) -> QString {
return utils::humanReadableFileSize(eventFilesize(e));
},
event));
case MimeType:
return QVariant(boost::apply_visitor(
[](const auto &e) -> QString { return eventMimeType(e); }, event));
case Height:
return QVariant(boost::apply_visitor(
[](const auto &e) -> qulonglong { return eventHeight(e); }, event));
case Width:
return QVariant(boost::apply_visitor(
[](const auto &e) -> qulonglong { return eventWidth(e); }, event));
case ProportionalHeight:
return QVariant(boost::apply_visitor(
[](const auto &e) -> double { return eventPropHeight(e); }, event));
// only show read receipts for messages not from us
if (boost::apply_visitor([](const auto &e) -> QString { return senderId(e); },
event)
.toStdString() != http::client()->user_id().to_string())
return qml_mtx_events::Empty;
else if (failed.contains(id))
return qml_mtx_events::Failed;
else if (pending.contains(id))
return qml_mtx_events::Sent;
else if (read.contains(id) ||
cache::client()->readReceipts(id, room_id_).size() > 1)
return qml_mtx_events::Read;
case IsEncrypted: {
auto tempEvent = events[id];
return boost::get<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
&tempEvent) != nullptr;
}
case ReplyTo: {
QString evId = boost::apply_visitor(
[](const auto &e) -> QString { return eventRelatesTo(e); }, event);
return QVariant(evId);
}
default:
return QVariant();
}
}
TimelineModel::addEvents(const mtx::responses::Timeline &timeline)
if (isInitialSync) {
prev_batch_token_ = QString::fromStdString(timeline.prev_batch);
isInitialSync = false;
}
if (timeline.events.empty())
return;
std::vector<QString> ids = internalAddEvents(timeline.events);
if (ids.empty())
return;
beginInsertRows(QModelIndex(),
static_cast<int>(this->eventOrder.size()),
static_cast<int>(this->eventOrder.size() + ids.size() - 1));
this->eventOrder.insert(this->eventOrder.end(), ids.begin(), ids.end());
endInsertRows();
updateLastMessage();
}
void
TimelineModel::updateLastMessage()
{
auto event = events.value(eventOrder.back());
if (auto e = boost::get<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&event)) {
event = decryptEvent(*e).event;
auto description = utils::getMessageDescription(
event, QString::fromStdString(http::client()->user_id().to_string()), room_id_);
emit manager_->updateRoomsLastMessage(room_id_, description);
}
std::vector<QString>
TimelineModel::internalAddEvents(
const std::vector<mtx::events::collections::TimelineEvents> &timeline)
{
QString id =
boost::apply_visitor([](const auto &e) -> QString { return eventId(e); }, e);
if (this->events.contains(id)) {
this->events.insert(id, e);
int idx = idToIndex(id);
emit dataChanged(index(idx, 0), index(idx, 0));
Nicolas Werner
committed
continue;
Nicolas Werner
committed
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
if (auto redaction =
boost::get<mtx::events::RedactionEvent<mtx::events::msg::Redaction>>(&e)) {
QString redacts = QString::fromStdString(redaction->redacts);
auto redacted = std::find(eventOrder.begin(), eventOrder.end(), redacts);
if (redacted != eventOrder.end()) {
auto redactedEvent = boost::apply_visitor(
[](const auto &ev)
-> mtx::events::RoomEvent<mtx::events::msg::Redacted> {
mtx::events::RoomEvent<mtx::events::msg::Redacted>
replacement = {};
replacement.event_id = ev.event_id;
replacement.room_id = ev.room_id;
replacement.sender = ev.sender;
replacement.origin_server_ts = ev.origin_server_ts;
replacement.type = ev.type;
return replacement;
},
e);
events.insert(redacts, redactedEvent);
int row = (int)std::distance(eventOrder.begin(), redacted);
emit dataChanged(index(row, 0), index(row, 0));
}
continue; // don't insert redaction into timeline
}
this->events.insert(id, e);
ids.push_back(id);
}
void
TimelineModel::fetchHistory()
{
if (paginationInProgress) {
nhlog::ui()->warn("Already loading older messages");
return;
}
paginationInProgress = true;
mtx::http::MessagesOpts opts;
opts.room_id = room_id_.toStdString();
opts.from = prev_batch_token_.toStdString();
nhlog::ui()->info("Paginationg room {}", opts.room_id);
http::client()->messages(
opts, [this, opts](const mtx::responses::Messages &res, mtx::http::RequestErr err) {
if (err) {
nhlog::net()->error("failed to call /messages ({}): {} - {}",
opts.room_id,
mtx::errors::to_string(err->matrix_error.errcode),
err->matrix_error.error);
paginationInProgress = false;
return;
}
emit oldMessagesRetrieved(std::move(res));
paginationInProgress = false;
void
TimelineModel::setCurrentIndex(int index)
{
auto oldIndex = idToIndex(currentId);
emit currentIndexChanged(index);
if (oldIndex < index && !pending.contains(currentId)) {
readEvent(currentId.toStdString());
void
TimelineModel::readEvent(const std::string &id)
{
http::client()->read_event(room_id_.toStdString(), id, [this](mtx::http::RequestErr err) {
if (err) {
nhlog::net()->warn("failed to read_event ({}, {})",
room_id_.toStdString(),
currentId.toStdString());
}
});
}
void
TimelineModel::addBackwardsEvents(const mtx::responses::Messages &msgs)
{
std::vector<QString> ids = internalAddEvents(msgs.chunk);
Nicolas Werner
committed
if (!ids.empty()) {
beginInsertRows(QModelIndex(), 0, static_cast<int>(ids.size() - 1));
this->eventOrder.insert(this->eventOrder.begin(), ids.rbegin(), ids.rend());
endInsertRows();
}
prev_batch_token_ = QString::fromStdString(msgs.end);
}
QColor
TimelineModel::userColor(QString id, QColor background)
{
id, QColor(utils::generateContrastingHexColor(id, background.name())));
return userColors.value(id);
QString
TimelineModel::displayName(QString id) const
{
return Cache::displayName(room_id_, id);
}
QString
TimelineModel::avatarUrl(QString id) const
{
return Cache::avatarUrl(room_id_, id);
}
QString
TimelineModel::formatDateSeparator(QDate date) const
{
auto now = QDateTime::currentDateTime();
QString fmt = QLocale::system().dateFormat(QLocale::LongFormat);
if (now.date().year() == date.year()) {
QRegularExpression rx("[^a-zA-Z]*y+[^a-zA-Z]*");
fmt = fmt.remove(rx);
}
return date.toString(fmt);
}
QString
TimelineModel::escapeEmoji(QString str) const
{
return utils::replaceEmoji(str);
}
void
TimelineModel::viewRawMessage(QString id) const
{
std::string ev = utils::serialize_event(events.value(id)).dump(4);
auto dialog = new dialogs::RawMessage(QString::fromStdString(ev));
Q_UNUSED(dialog);
}
void
TimelineModel::openUserProfile(QString userid) const
{
MainWindow::instance()->openUserProfile(userid, room_id_);
}
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
DecryptionResult
TimelineModel::decryptEvent(const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e) const
{
MegolmSessionIndex index;
index.room_id = room_id_.toStdString();
index.session_id = e.content.session_id;
index.sender_key = e.content.sender_key;
mtx::events::RoomEvent<mtx::events::msg::Notice> dummy;
dummy.origin_server_ts = e.origin_server_ts;
dummy.event_id = e.event_id;
dummy.sender = e.sender;
dummy.content.body =
tr("-- Encrypted Event (No keys found for decryption) --",
"Placeholder, when the message was not decrypted yet or can't be decrypted")
.toStdString();
try {
if (!cache::client()->inboundMegolmSessionExists(index)) {
nhlog::crypto()->info("Could not find inbound megolm session ({}, {}, {})",
index.room_id,
index.session_id,
e.sender);
// TODO: request megolm session_id & session_key from the sender.
return {dummy, false};
}
} catch (const lmdb::error &e) {
nhlog::db()->critical("failed to check megolm session's existence: {}", e.what());
dummy.content.body = tr("-- Decryption Error (failed to communicate with DB) --",
"Placeholder, when the message can't be decrypted, because "
"the DB access failed when trying to lookup the session.")
.toStdString();
return {dummy, false};
}
std::string msg_str;
try {
auto session = cache::client()->getInboundMegolmSession(index);
auto res = olm::client()->decrypt_group_message(session, e.content.ciphertext);
msg_str = std::string((char *)res.data.data(), res.data.size());
} catch (const lmdb::error &e) {
nhlog::db()->critical("failed to retrieve megolm session with index ({}, {}, {})",
index.room_id,
index.session_id,
index.sender_key,
e.what());
dummy.content.body =
tr("-- Decryption Error (failed to retrieve megolm keys from db) --",
"Placeholder, when the message can't be decrypted, because the DB access "
"failed.")
.toStdString();
return {dummy, false};
} catch (const mtx::crypto::olm_exception &e) {
nhlog::crypto()->critical("failed to decrypt message with index ({}, {}, {}): {}",
index.room_id,
index.session_id,
index.sender_key,
e.what());
dummy.content.body =
tr("-- Decryption Error (%1) --",
"Placeholder, when the message can't be decrypted. In this case, the Olm "
"decrytion returned an error, which is passed ad %1")
.arg(e.what())
.toStdString();
return {dummy, false};
}
// Add missing fields for the event.
json body = json::parse(msg_str);
body["event_id"] = e.event_id;
body["sender"] = e.sender;
body["origin_server_ts"] = e.origin_server_ts;
body["unsigned"] = e.unsigned_data;
json event_array = json::array();
event_array.push_back(body);
std::vector<mtx::events::collections::TimelineEvents> temp_events;
mtx::responses::utils::parse_timeline_events(event_array, temp_events);
if (temp_events.size() == 1)
return {temp_events.at(0), true};
dummy.content.body =
tr("-- Encrypted Event (Unknown event type) --",
"Placeholder, when the message was decrypted, but we couldn't parse it, because "
"Nheko/mtxclient don't support that event type yet")
.toStdString();
return {dummy, false};
}
void
TimelineModel::replyAction(QString id)
{
auto event = events.value(id);
RelatedInfo related = boost::apply_visitor(
[](const auto &ev) -> RelatedInfo {
RelatedInfo related_ = {};
related_.quoted_user = QString::fromStdString(ev.sender);
related_.related_event = ev.event_id;
return related_;
},
event);
related.type = mtx::events::getMessageType(boost::apply_visitor(
[](const auto &e) -> std::string { return eventMsgType(e); }, event));
related.quoted_body = boost::apply_visitor(
[](const auto &e) -> QString { return eventFormattedBody(e); }, event);
related.quoted_body.remove(QRegularExpression(
"<mx-reply>.*</mx-reply>", QRegularExpression::DotMatchesEverythingOption));
nhlog::ui()->debug("after replacement: {}", related.quoted_body.toStdString());
if (related.quoted_body.isEmpty())
return;
ChatPage::instance()->messageReply(related);
}
void
TimelineModel::readReceiptsAction(QString id) const
{
MainWindow::instance()->openReadReceiptsDialog(id);
void
TimelineModel::redactEvent(QString id)
{
if (!id.isEmpty())
http::client()->redact_event(
room_id_.toStdString(),
id.toStdString(),
[this, id](const mtx::responses::EventId &, mtx::http::RequestErr err) {
if (err) {
emit redactionFailed(
tr("Message redaction failed: %1")
.arg(QString::fromStdString(err->matrix_error.error)));
return;
}
emit eventRedacted(id);
});
}
int
TimelineModel::idToIndex(QString id) const
{
if (id.isEmpty())
return -1;
for (int i = 0; i < (int)eventOrder.size(); i++)
if (id == eventOrder[i])
return i;
return -1;
}
QString
TimelineModel::indexToId(int index) const
{
if (index < 0 || index >= (int)eventOrder.size())
return "";
return eventOrder[index];
}
void
TimelineModel::markEventsAsRead(const std::vector<QString> &event_ids)
{
for (const auto &id : event_ids) {
read.insert(id);
int idx = idToIndex(id);
if (idx < 0) {
nhlog::ui()->warn("Read index out of range");
return;
}
emit dataChanged(index(idx, 0), index(idx, 0));
}
}
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
void
TimelineModel::sendEncryptedMessage(const std::string &txn_id, nlohmann::json content)
{
const auto room_id = room_id_.toStdString();
using namespace mtx::events;
using namespace mtx::identifiers;
json doc{{"type", "m.room.message"}, {"content", content}, {"room_id", room_id}};
try {
// Check if we have already an outbound megolm session then we can use.
if (cache::client()->outboundMegolmSessionExists(room_id)) {
auto data = olm::encrypt_group_message(
room_id, http::client()->device_id(), doc.dump());
http::client()->send_room_message<msg::Encrypted, EventType::RoomEncrypted>(
room_id,
txn_id,
data,
[this, txn_id](const mtx::responses::EventId &res,
mtx::http::RequestErr err) {
if (err) {
const int status_code =
static_cast<int>(err->status_code);
nhlog::net()->warn("[{}] failed to send message: {} {}",
txn_id,
err->matrix_error.error,
status_code);
emit messageFailed(QString::fromStdString(txn_id));
}
emit messageSent(
QString::fromStdString(txn_id),
QString::fromStdString(res.event_id.to_string()));
});
return;
}
nhlog::ui()->debug("creating new outbound megolm session");
// Create a new outbound megolm session.
auto outbound_session = olm::client()->init_outbound_group_session();
const auto session_id = mtx::crypto::session_id(outbound_session.get());
const auto session_key = mtx::crypto::session_key(outbound_session.get());
// TODO: needs to be moved in the lib.
auto megolm_payload = json{{"algorithm", "m.megolm.v1.aes-sha2"},
{"room_id", room_id},
{"session_id", session_id},
{"session_key", session_key}};
// Saving the new megolm session.
// TODO: Maybe it's too early to save.
OutboundGroupSessionData session_data;
session_data.session_id = session_id;
session_data.session_key = session_key;
session_data.message_index = 0; // TODO Update me
cache::client()->saveOutboundMegolmSession(
room_id, session_data, std::move(outbound_session));
const auto members = cache::client()->roomMembers(room_id);
nhlog::ui()->info("retrieved {} members for {}", members.size(), room_id);
auto keeper =
std::make_shared<StateKeeper>([megolm_payload, room_id, doc, txn_id, this]() {
try {
auto data = olm::encrypt_group_message(
room_id, http::client()->device_id(), doc.dump());
http::client()
->send_room_message<msg::Encrypted, EventType::RoomEncrypted>(
room_id,
txn_id,
data,
[this, txn_id](const mtx::responses::EventId &res,
mtx::http::RequestErr err) {
if (err) {
const int status_code =
static_cast<int>(err->status_code);
nhlog::net()->warn(