diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index aa89f0ca1..a9d8edaa0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -134,6 +134,8 @@ add_library(neochat STATIC jobs/neochatdeletedevicejob.h jobs/neochatchangepasswordjob.cpp jobs/neochatchangepasswordjob.h + jobs/neochatgetcommonroomsjob.cpp + jobs/neochatgetcommonroomsjob.h mediasizehelper.cpp mediasizehelper.h eventhandler.cpp diff --git a/src/jobs/neochatgetcommonroomsjob.cpp b/src/jobs/neochatgetcommonroomsjob.cpp new file mode 100644 index 000000000..c0301f189 --- /dev/null +++ b/src/jobs/neochatgetcommonroomsjob.cpp @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2024 Joshua Goins +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "neochatgetcommonroomsjob.h" + +using namespace Quotient; + +NeochatGetCommonRoomsJob::NeochatGetCommonRoomsJob(const QString &userId) + : BaseJob(HttpVerb::Get, + QStringLiteral("GetCommonRoomsJob"), + QStringLiteral("/_matrix/client/unstable/uk.half-shot.msc2666/user/mutual_rooms").toLatin1(), + QUrlQuery({{QStringLiteral("user_id"), userId}})) +{ +} diff --git a/src/jobs/neochatgetcommonroomsjob.h b/src/jobs/neochatgetcommonroomsjob.h new file mode 100644 index 000000000..f7817d53c --- /dev/null +++ b/src/jobs/neochatgetcommonroomsjob.h @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2024 Joshua Goins +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +// TODO: Upstream to libQuotient +class NeochatGetCommonRoomsJob : public Quotient::BaseJob +{ +public: + explicit NeochatGetCommonRoomsJob(const QString &userId); +}; diff --git a/src/neochatconfig.kcfg b/src/neochatconfig.kcfg index 4dff7510b..c1ef7bfa0 100644 --- a/src/neochatconfig.kcfg +++ b/src/neochatconfig.kcfg @@ -187,5 +187,11 @@ false + + + + false + + diff --git a/src/neochatconnection.cpp b/src/neochatconnection.cpp index 89fbcd61a..2b2f7ebcb 100644 --- a/src/neochatconnection.cpp +++ b/src/neochatconnection.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -132,6 +133,21 @@ void NeoChatConnection::connectSignals() Q_EMIT homeNotificationsChanged(); Q_EMIT homeHaveHighlightNotificationsChanged(); }); + + // Fetch unstable features + // TODO: Expose unstableFeatures() in libQuotient + connect( + this, + &Connection::connected, + this, + [this] { + auto job = callApi(BackgroundRequest); + connect(job, &GetVersionsJob::success, this, [this, job] { + m_canCheckMutualRooms = job->unstableFeatures().contains("uk.half-shot.msc2666.query_mutual_rooms"_ls); + Q_EMIT canCheckMutualRoomsChanged(); + }); + }, + Qt::SingleShotConnection); } int NeoChatConnection::badgeNotificationCount() const @@ -200,6 +216,11 @@ QVariantList NeoChatConnection::getSupportedRoomVersions() const return supportedRoomVersions; } +bool NeoChatConnection::canCheckMutualRooms() const +{ + return m_canCheckMutualRooms; +} + void NeoChatConnection::changePassword(const QString ¤tPassword, const QString &newPassword) { auto job = callApi(newPassword, false); diff --git a/src/neochatconnection.h b/src/neochatconnection.h index 4c57766ee..81c4a2f31 100644 --- a/src/neochatconnection.h +++ b/src/neochatconnection.h @@ -79,6 +79,11 @@ class NeoChatConnection : public Quotient::Connection */ Q_PROPERTY(bool isOnline READ isOnline WRITE setIsOnline NOTIFY isOnlineChanged) + /** + * @brief Whether the server supports querying a user's mutual rooms. + */ + Q_PROPERTY(bool canCheckMutualRooms READ canCheckMutualRooms NOTIFY canCheckMutualRoomsChanged) + public: /** * @brief Defines the status after an attempt to change the password on an account. @@ -95,6 +100,7 @@ public: Q_INVOKABLE void logout(bool serverSideLogout); Q_INVOKABLE QVariantList getSupportedRoomVersions() const; + bool canCheckMutualRooms() const; /** * @brief Change the password for an account. @@ -196,6 +202,7 @@ Q_SIGNALS: void passwordStatus(NeoChatConnection::PasswordStatus status); void userConsentRequired(QUrl url); void badgeNotificationCountChanged(NeoChatConnection *connection, int count); + void canCheckMutualRoomsChanged(); private: bool m_isOnline = true; @@ -208,4 +215,6 @@ private: int m_badgeNotificationCount = 0; QHash m_linkPreviewers; + + bool m_canCheckMutualRooms = false; }; diff --git a/src/neochatroom.cpp b/src/neochatroom.cpp index 0d54e0dc4..569c6358e 100644 --- a/src/neochatroom.cpp +++ b/src/neochatroom.cpp @@ -41,6 +41,7 @@ #include "events/joinrulesevent.h" #include "events/pollevent.h" #include "filetransferpseudojob.h" +#include "jobs/neochatgetcommonroomsjob.h" #include "neochatconfig.h" #include "notificationsmanager.h" #include "roomlastmessageprovider.h" @@ -129,14 +130,38 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS return; } auto roomMemberEvent = currentState().get(localMember().id()); - QImage avatar_image; - if (roomMemberEvent && !member(roomMemberEvent->senderId()).avatarUrl().isEmpty()) { - avatar_image = memberAvatar(roomMemberEvent->senderId()).get(this->connection(), 128, [] {}); + + auto showNotification = [this, roomMemberEvent] { + QImage avatar_image; + if (roomMemberEvent && !member(roomMemberEvent->senderId()).avatarUrl().isEmpty()) { + avatar_image = memberAvatar(roomMemberEvent->senderId()).get(this->connection(), 128, [] {}); + } else { + qWarning() << "using this room's avatar"; + avatar_image = avatar(128); + } + + NotificationsManager::instance().postInviteNotification(this, + displayName(), + member(roomMemberEvent->senderId()).htmlSafeDisplayName(), + avatar_image); + }; + + if (NeoChatConfig::rejectUnknownInvites()) { + auto job = this->connection()->callApi(roomMemberEvent->senderId()); + connect(job, &BaseJob::result, this, [this, job, roomMemberEvent, showNotification] { + QJsonObject replyData = job->jsonData(); + if (replyData.contains(QStringLiteral("joined"))) { + const bool inAnyOfOurRooms = !replyData[QStringLiteral("joined")].toArray().isEmpty(); + if (inAnyOfOurRooms) { + showNotification(); + } else { + leaveRoom(); + } + } + }); } else { - qWarning() << "using this room's avatar"; - avatar_image = avatar(128); + showNotification(); } - NotificationsManager::instance().postInviteNotification(this, displayName(), member(roomMemberEvent->senderId()).htmlSafeDisplayName(), avatar_image); }, Qt::SingleShotConnection); connect(this, &Room::changed, this, [this] { @@ -1313,7 +1338,6 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state) m_currentPushNotificationState = state; Q_EMIT pushNotificationStateChanged(m_currentPushNotificationState); - } void NeoChatRoom::updatePushNotificationState(QString type) diff --git a/src/settings/NeoChatSecurityPage.qml b/src/settings/NeoChatSecurityPage.qml index e71975b4d..8fa5a72ec 100644 --- a/src/settings/NeoChatSecurityPage.qml +++ b/src/settings/NeoChatSecurityPage.qml @@ -16,6 +16,32 @@ FormCard.FormCardPage { title: i18nc("@title", "Security") + FormCard.FormHeader { + title: i18nc("@title:group", "Invitations") + } + FormCard.FormCard { + FormCard.FormCheckDelegate { + text: i18nc("@option:check", "Reject invitations from unknown users") + description: connection.canCheckMutualRooms ? i18n("If enabled, NeoChat will reject invitations from from users you don't share a room with.") : i18n("Your server does not support this setting.") + checked: Config.rejectUnknownInvites + enabled: !Config.isRejectUnknownInvitesImmutable && connection.canCheckMutualRooms + onToggled: { + Config.rejectUnknownInvites = checked; + Config.save(); + } + } + } + FormCard.FormHeader { + title: i18nc("@title:group", "Ignored Users") + } + FormCard.FormCard { + FormCard.FormButtonDelegate { + text: i18nc("@action:button", "Manage ignored users") + onClicked: root.ApplicationWindow.window.pageStack.push(ignoredUsersDialogComponent, {}, { + title: i18nc("@title:window", "Ignored Users") + }); + } + } FormCard.FormHeader { title: i18nc("@title", "Keys") } @@ -33,17 +59,6 @@ FormCard.FormCardPage { description: i18n("Device id") } } - FormCard.FormHeader { - title: i18nc("@title:group", "Ignored Users") - } - FormCard.FormCard { - FormCard.FormButtonDelegate { - text: i18nc("@action:button", "Manage ignored users") - onClicked: pageStack.pushDialogLayer(ignoredUsersDialogComponent, {}, { - title: i18nc("@title:window", "Ignored Users") - }); - } - } Component { id: ignoredUsersDialogComponent