diff --git a/man/nheko.1.adoc b/man/nheko.1.adoc
index 0a1b1568..82053af0 100644
--- a/man/nheko.1.adoc
+++ b/man/nheko.1.adoc
@@ -178,6 +178,12 @@ Ban a user from the current room. _reason_ is optional.
*/unban* _<username>_ _[reason]_::
Unban a user. _reason_ is optional.
+*/redact* _<username>_ _[reason]_::
+Redacts all visible messages of the specified user. You will run into rate limits quickly.
+
+*/redact* _<eventid>_ _[reason]_::
+Redacts a specific event.
+
*/roomnick* _<roomname>_::
Change your nickname in a single room.
diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp
index fe171deb..91654f40 100644
--- a/src/timeline/InputBar.cpp
+++ b/src/timeline/InputBar.cpp
@@ -692,6 +692,12 @@ InputBar::command(const QString &command, QString args)
} else if (command == QLatin1String("unban")) {
ChatPage::instance()->unbanUser(
room->roomId(), args.section(' ', 0, 0), args.section(' ', 1, -1));
+ } else if (command == QLatin1String("redact")) {
+ if (args.startsWith('@')) {
+ room->redactAllFromUser(args.section(' ', 0, 0), args.section(' ', 1, -1));
+ } else if (args.startsWith('$')) {
+ room->redactEvent(args.section(' ', 0, 0), args.section(' ', 1, -1));
+ }
} else if (command == QLatin1String("roomnick")) {
mtx::events::state::Member member;
member.display_name = args.toStdString();
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 3fe4c07f..ad0a8c11 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -1272,6 +1272,24 @@ TimelineModel::showReadReceipts(QString id)
}
void
+TimelineModel::redactAllFromUser(const QString &userid, const QString &reason)
+{
+ auto user = userid.toStdString();
+ std::vector<QString> toRedact;
+ for (auto it = events.size() - 1; it >= 0; --it) {
+ auto event = events.get(it, false);
+ if (event && mtx::accessors::sender(*event) == user &&
+ !std::holds_alternative<mtx::events::RoomEvent<mtx::events::msg::Redacted>>(*event)) {
+ toRedact.push_back(QString::fromStdString(mtx::accessors::event_id(*event)));
+ }
+ }
+
+ for (const auto &e : toRedact) {
+ redactEvent(e, reason);
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
+ }
+}
+void
TimelineModel::redactEvent(const QString &id, const QString &reason)
{
if (!id.isEmpty()) {
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index 3b954394..dcafae80 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -276,6 +276,7 @@ public:
Q_INVOKABLE void pin(const QString &id);
Q_INVOKABLE void showReadReceipts(QString id);
Q_INVOKABLE void redactEvent(const QString &id, const QString &reason = "");
+ Q_INVOKABLE void redactAllFromUser(const QString &userid, const QString &reason = "");
Q_INVOKABLE int idToIndex(const QString &id) const;
Q_INVOKABLE QString indexToId(int index) const;
Q_INVOKABLE void openMedia(const QString &eventId);
|