diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index bfd95b0d..30ce176e 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -827,6 +827,16 @@ TimelineModel::viewRawMessage(QString id) const
}
void
+TimelineModel::forwardMessage(QString eventId, QString roomId)
+{
+ auto e = events.get(eventId.toStdString(), "");
+ if (!e)
+ return;
+
+ emit forwardToRoom(e, roomId);
+}
+
+void
TimelineModel::viewDecryptedRawMessage(QString id) const
{
auto e = events.get(id.toStdString(), "");
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index 06da95c6..fbe963d2 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -219,6 +219,7 @@ public:
Q_INVOKABLE QString formatPowerLevelEvent(QString id);
Q_INVOKABLE void viewRawMessage(QString id) const;
+ Q_INVOKABLE void forwardMessage(QString eventId, QString roomId);
Q_INVOKABLE void viewDecryptedRawMessage(QString id) const;
Q_INVOKABLE void openUserProfile(QString userid, bool global = false);
Q_INVOKABLE void openRoomSettings();
@@ -322,6 +323,7 @@ signals:
void roomNameChanged();
void roomTopicChanged();
void roomAvatarUrlChanged();
+ void forwardToRoom(mtx::events::collections::TimelineEvents *e, QString roomId);
private:
template<typename T>
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index e9986c7e..518ccaf1 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -31,8 +31,6 @@
#include "ui/NhekoCursorShape.h"
#include "ui/NhekoDropArea.h"
-#include <iostream> //only for debugging
-
Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents)
Q_DECLARE_METATYPE(std::vector<DeviceInfo>)
@@ -329,6 +327,10 @@ TimelineViewManager::addRoom(const QString &room_id)
&TimelineModel::newEncryptedImage,
imgProvider,
&MxcImageProvider::addEncryptionInfo);
+ connect(newRoom.data(),
+ &TimelineModel::forwardToRoom,
+ this,
+ &TimelineViewManager::forwardMessageToRoom);
models.insert(room_id, std::move(newRoom));
}
}
@@ -616,3 +618,80 @@ TimelineViewManager::focusTimeline()
{
getWidget()->setFocus();
}
+
+void
+TimelineViewManager::forwardMessageToRoom(mtx::events::collections::TimelineEvents *e,
+ QString roomId)
+{
+ auto room = models.find(roomId);
+ auto content = mtx::accessors::url(*e);
+ std::optional<mtx::crypto::EncryptedFile> encryptionInfo = mtx::accessors::file(*e);
+
+ if (encryptionInfo) {
+ http::client()->download(
+ content,
+ [this, roomId, e, encryptionInfo](const std::string &res,
+ const std::string &content_type,
+ const std::string &originalFilename,
+ mtx::http::RequestErr err) {
+ if (err)
+ return;
+
+ auto data = mtx::crypto::to_string(
+ mtx::crypto::decrypt_file(res, encryptionInfo.value()));
+
+ http::client()->upload(
+ data,
+ content_type,
+ originalFilename,
+ [this, roomId, e](const mtx::responses::ContentURI &res,
+ mtx::http::RequestErr err) mutable {
+ if (err) {
+ nhlog::net()->warn("failed to upload media: {} {} ({})",
+ err->matrix_error.error,
+ to_string(err->matrix_error.errcode),
+ static_cast<int>(err->status_code));
+ return;
+ }
+
+ std::visit(
+ [this, roomId, url = res.content_uri](auto ev) {
+ if constexpr (mtx::events::message_content_to_type<
+ decltype(ev.content)> ==
+ mtx::events::EventType::RoomMessage) {
+ if constexpr (messageWithFileAndUrl(ev)) {
+ ev.content.relations.relations
+ .clear();
+ ev.content.file.reset();
+ ev.content.url = url;
+ }
+
+ auto room = models.find(roomId);
+ removeReplyFallback(ev);
+ ev.content.relations.relations.clear();
+ room.value()->sendMessageEvent(
+ ev.content,
+ mtx::events::EventType::RoomMessage);
+ }
+ },
+ *e);
+ });
+
+ return;
+ });
+
+ return;
+ }
+
+ std::visit(
+ [room](auto e) {
+ if constexpr (mtx::events::message_content_to_type<decltype(e.content)> ==
+ mtx::events::EventType::RoomMessage) {
+ e.content.relations.relations.clear();
+ removeReplyFallback(e);
+ room.value()->sendMessageEvent(e.content,
+ mtx::events::EventType::RoomMessage);
+ }
+ },
+ *e);
+}
\ No newline at end of file
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 3b405142..809b286e 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -16,6 +16,7 @@
#include "Cache.h"
#include "CallManager.h"
+#include "EventAccessors.h"
#include "Logging.h"
#include "TimelineModel.h"
#include "Utils.h"
@@ -146,11 +147,59 @@ public slots:
void backToRooms() { emit showRoomList(); }
QObject *completerFor(QString completerName, QString roomId = "");
+ void forwardMessageToRoom(mtx::events::collections::TimelineEvents *e, QString roomId);
private slots:
void openImageOverlayInternal(QString eventId, QImage img);
private:
+ template<template<class...> class Op, class... Args>
+ using is_detected =
+ typename nheko::detail::detector<nheko::nonesuch, void, Op, Args...>::value_t;
+
+ template<class Content>
+ using file_t = decltype(Content::file);
+
+ template<class Content>
+ using url_t = decltype(Content::url);
+
+ template<class Content>
+ using body_t = decltype(Content::body);
+
+ template<class Content>
+ using formatted_body_t = decltype(Content::formatted_body);
+
+ template<typename T>
+ static constexpr bool messageWithFileAndUrl(const mtx::events::Event<T> &)
+ {
+ return is_detected<file_t, T>::value && is_detected<url_t, T>::value;
+ }
+
+ template<typename T>
+ static constexpr void removeReplyFallback(mtx::events::Event<T> &e)
+ {
+ if constexpr (is_detected<body_t, T>::value) {
+ if constexpr (std::is_same_v<std::optional<std::string>,
+ std::remove_cv_t<decltype(e.content.body)>>) {
+ if (e.content.body) {
+ e.content.body = utils::stripReplyFromBody(e.content.body);
+ }
+ } else if constexpr (std::is_same_v<
+ std::string,
+ std::remove_cv_t<decltype(e.content.body)>>) {
+ e.content.body = utils::stripReplyFromBody(e.content.body);
+ }
+ }
+
+ if constexpr (is_detected<formatted_body_t, T>::value) {
+ if (e.content.format == "org.matrix.custom.html") {
+ e.content.formatted_body =
+ utils::stripReplyFromFormattedBody(e.content.formatted_body);
+ }
+ }
+ }
+
+private:
#ifdef USE_QUICK_VIEW
QQuickView *view;
#else
|