diff --git a/man/nheko.1.adoc b/man/nheko.1.adoc index cc4b8f74..5bd5d622 100644 --- a/man/nheko.1.adoc +++ b/man/nheko.1.adoc @@ -229,6 +229,14 @@ Inserts `┯━┯╭( º _ º╭)` */sovietflip*:: Inserts `ノ┬─┬ノ ︵ ( \\o°o)\\` +=== User management + +*/ignore* __:: +Ignore a user, invites from them are also rejected. + +*/unignore* __:: +Stops ignoring a user. + === Advanced */clear-timeline*:: diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index f66df94f..0a255491 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -277,6 +277,16 @@ Item { onClicked: Rooms.declineInvite(roomPreview.roomid) } + FlatButton { + Layout.alignment: Qt.AlignHCenter + text: qsTr("decline invite and ignore user") + visible: roomPreview && roomPreview.isInvite + + onClicked: { + var inviter = TimelineManager.getGlobalUserProfile(roomPreview.inviterUserId) + inviter.ignored = true + } + } FlatButton { Layout.alignment: Qt.AlignHCenter text: qsTr("leave") diff --git a/src/CommandCompleter.cpp b/src/CommandCompleter.cpp index 8123b8e6..ee666559 100644 --- a/src/CommandCompleter.cpp +++ b/src/CommandCompleter.cpp @@ -97,6 +97,10 @@ CommandCompleter::data(const QModelIndex &index, int role) const return QStringLiteral("/converttodm"); case ConvertToRoom: return QStringLiteral("/converttoroom"); + case Ignore: + return QStringLiteral("/ignore"); + case Unignore: + return QStringLiteral("/unignore"); default: return {}; } @@ -170,6 +174,10 @@ CommandCompleter::data(const QModelIndex &index, int role) const return QStringLiteral("/converttodm"); case ConvertToRoom: return QStringLiteral("/converttoroom"); + case Ignore: + return QStringLiteral("/ignore <@userid>"); + case Unignore: + return QStringLiteral("/unignore <@userid>"); default: return {}; } @@ -243,6 +251,10 @@ CommandCompleter::data(const QModelIndex &index, int role) const return tr("Convert this room to a direct chat."); case ConvertToRoom: return tr("Convert this direct chat into a room."); + case Ignore: + return tr("Ignore a user."); + case Unignore: + return tr("Stop ignoring a user."); default: return {}; } diff --git a/src/CommandCompleter.h b/src/CommandCompleter.h index 4f27fe29..35ce2b36 100644 --- a/src/CommandCompleter.h +++ b/src/CommandCompleter.h @@ -51,6 +51,8 @@ public: Goto, ConvertToDm, ConvertToRoom, + Ignore, + Unignore, COUNT, }; diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index d37e403f..03ae5658 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -30,6 +30,7 @@ #include "ChatPage.h" #include "EventAccessors.h" #include "Logging.h" +#include "MainWindow.h" #include "MatrixClient.h" #include "TimelineModel.h" #include "TimelineViewManager.h" @@ -239,7 +240,9 @@ InputBar::updateTextContentProperties(const QString &t) QStringLiteral("msgtype"), QStringLiteral("goto"), QStringLiteral("converttodm"), - QStringLiteral("converttoroom")}; + QStringLiteral("converttoroom"), + QStringLiteral("ignore"), + QStringLiteral("unignore")}; bool hasInvalidCommand = !commandName.isNull() && !validCommands.contains(commandName); bool hasIncompleteCommand = hasInvalidCommand && '/' + commandName == t; @@ -937,6 +940,10 @@ InputBar::command(const QString &command, QString args) cache::getMembers(this->room->roomId().toStdString(), 0, -1)); } else if (command == QLatin1String("converttoroom")) { utils::removeDirectFromRoom(this->room->roomId()); + } else if (command == QLatin1String("ignore")) { + this->toggleIgnore(args, true); + } else if (command == QLatin1String("unignore")) { + this->toggleIgnore(args, false); } else { return false; } @@ -944,6 +951,23 @@ InputBar::command(const QString &command, QString args) return true; } +void +InputBar::toggleIgnore(const QString &user, const bool ignored) +{ + UserProfile *profile = new UserProfile(QString(), user, TimelineViewManager::instance()); + connect(profile, &UserProfile::failedToFetchProfile, [user, profile] { + MainWindow::instance()->showNotification(tr("Failed to fetch user %1").arg(user)); + profile->deleteLater(); + }); + + connect( + profile, &UserProfile::globalUsernameRetrieved, [profile, ignored](const QString &user_id) { + Q_UNUSED(user_id) + profile->setIgnored(ignored); + profile->deleteLater(); + }); +} + MediaUpload::MediaUpload(std::unique_ptr source_, const QString &mimetype, const QString &originalFilename, diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h index b15377fc..fbf08343 100644 --- a/src/timeline/InputBar.h +++ b/src/timeline/InputBar.h @@ -283,6 +283,8 @@ private: void updateTextContentProperties(const QString &t); + void toggleIgnore(const QString &user, const bool ignored); + QTimer typingRefresh_; QTimer typingTimeout_; TimelineModel *room; diff --git a/src/timeline/RoomlistModel.h b/src/timeline/RoomlistModel.h index 34bf3f9a..2294864f 100644 --- a/src/timeline/RoomlistModel.h +++ b/src/timeline/RoomlistModel.h @@ -197,6 +197,8 @@ public: return instance_; } + static FilteredRoomlistModel *instance() { return instance_; } + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; bool filterAcceptsRow(int sourceRow, const QModelIndex &) const override; diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index 338f3658..c7254e23 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -278,6 +278,17 @@ UserProfile::setIgnored(bool ignore) .arg(userid, QString::fromStdString(e->matrix_error.error))); } }); + + if (ignore) { + const QHash invites = cache::invites(); + FilteredRoomlistModel *room_model = FilteredRoomlistModel::instance(); + + for (auto room = invites.keyBegin(), end = invites.keyEnd(); room != end; room++) { + if (room_model->getRoomPreviewById(*room).inviterUserId() == userid) { + room_model->declineInvite(*room); + } + } + } } void @@ -592,12 +603,18 @@ UserProfile::getGlobalProfileData() emit avatarUrlChanged(); }); + connect(profProx.get(), + &UserProfileFetchProxy::failedToFetchProfile, + this, + &UserProfile::failedToFetchProfile); + http::client()->get_profile(userid_.toStdString(), [prox = std::move(profProx), user = userid_.toStdString()]( const mtx::responses::Profile &res, mtx::http::RequestErr err) { if (err) { nhlog::net()->warn("failed to retrieve profile info for {}", user); + emit prox->failedToFetchProfile(); return; } diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h index bc5b6a35..64dbf99c 100644 --- a/src/ui/UserProfile.h +++ b/src/ui/UserProfile.h @@ -45,6 +45,7 @@ public: signals: void profileFetched(mtx::responses::Profile); + void failedToFetchProfile(); }; class DeviceInfo @@ -205,6 +206,7 @@ signals: void globalUsernameRetrieved(const QString &globalUser); void devicesChanged(); void ignoredChanged(); + void failedToFetchProfile(); // internal void verificationStatiChanged();