diff --git a/src/Cache.cpp b/src/Cache.cpp
index e841a9dc..863f0683 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -1518,10 +1518,6 @@ Cache::updateReadReceipt(lmdb::txn &txn, const std::string &room_id, const Recei
// Append the new ones.
for (const auto &[read_by, timestamp] : event_receipts) {
- if (read_by == user_id) {
- emit removeNotification(QString::fromStdString(room_id),
- QString::fromStdString(event_id));
- }
saved_receipts.emplace(read_by, timestamp);
}
@@ -1818,12 +1814,6 @@ Cache::saveState(const mtx::responses::Sync &res)
updatedInfo.tags.push_back(tag.first);
}
}
- if (auto fr = std::get_if<
- mtx::events::AccountDataEvent<mtx::events::account_data::FullyRead>>(&evt)) {
- nhlog::db()->debug("Fully read: {}", fr->content.event_id);
- emit removeNotification(QString::fromStdString(room.first),
- QString::fromStdString(fr->content.event_id));
- }
}
}
@@ -2134,27 +2124,6 @@ Cache::roomIds()
return rooms;
}
-QMap<QString, mtx::responses::Notifications>
-Cache::getTimelineMentions()
-{
- // TODO: Should be read-only, but getMentionsDb will attempt to create a DB
- // if it doesn't exist, throwing an error.
- auto txn = lmdb::txn::begin(env_, nullptr);
-
- QMap<QString, mtx::responses::Notifications> notifs;
-
- auto room_ids = getRoomIds(txn);
-
- for (const auto &room_id : room_ids) {
- auto roomNotifs = getTimelineMentionsForRoom(txn, room_id);
- notifs[QString::fromStdString(room_id)] = roomNotifs;
- }
-
- txn.commit();
-
- return notifs;
-}
-
std::string
Cache::previousBatchToken(const std::string &room_id)
{
@@ -3587,88 +3556,6 @@ Cache::clearTimeline(const std::string &room_id)
txn.commit();
}
-mtx::responses::Notifications
-Cache::getTimelineMentionsForRoom(lmdb::txn &txn, const std::string &room_id)
-{
- auto db = getMentionsDb(txn, room_id);
-
- if (db.size(txn) == 0) {
- return mtx::responses::Notifications{};
- }
-
- mtx::responses::Notifications notif;
- std::string_view event_id, msg;
-
- auto cursor = lmdb::cursor::open(txn, db);
-
- while (cursor.get(event_id, msg, MDB_NEXT)) {
- auto obj = nlohmann::json::parse(msg);
-
- if (obj.count("event") == 0)
- continue;
-
- mtx::responses::Notification notification;
- from_json(obj, notification);
-
- notif.notifications.push_back(notification);
- }
- cursor.close();
-
- std::reverse(notif.notifications.begin(), notif.notifications.end());
-
- return notif;
-}
-
-//! Add all notifications containing a user mention to the db.
-void
-Cache::saveTimelineMentions(const mtx::responses::Notifications &res)
-{
- QMap<std::string, QList<mtx::responses::Notification>> notifsByRoom;
-
- // Sort into room-specific 'buckets'
- for (const auto ¬if : res.notifications) {
- nlohmann::json val = notif;
- notifsByRoom[notif.room_id].push_back(notif);
- }
-
- auto txn = lmdb::txn::begin(env_);
- // Insert the entire set of mentions for each room at a time.
- QMap<std::string, QList<mtx::responses::Notification>>::const_iterator it =
- notifsByRoom.constBegin();
- auto end = notifsByRoom.constEnd();
- while (it != end) {
- nhlog::db()->debug("Storing notifications for " + it.key());
- saveTimelineMentions(txn, it.key(), std::move(it.value()));
- ++it;
- }
-
- txn.commit();
-}
-
-void
-Cache::saveTimelineMentions(lmdb::txn &txn,
- const std::string &room_id,
- const QList<mtx::responses::Notification> &res)
-{
- auto db = getMentionsDb(txn, room_id);
-
- using namespace mtx::events;
- using namespace mtx::events::state;
-
- for (const auto ¬if : res) {
- const auto event_id = mtx::accessors::event_id(notif.event);
-
- // double check that we have the correct room_id...
- if (room_id.compare(notif.room_id) != 0) {
- return;
- }
-
- nlohmann::json obj = notif;
-
- db.put(txn, event_id, obj.dump());
- }
-}
-
void
Cache::markSentNotification(const std::string &event_id)
{
@@ -5299,12 +5186,6 @@ roomIds()
return instance_->roomIds();
}
-QMap<QString, mtx::responses::Notifications>
-getTimelineMentions()
-{
- return instance_->getTimelineMentions();
-}
-
//! Retrieve all the user ids from a room.
std::vector<std::string>
roomMembers(const std::string &room_id)
@@ -5405,13 +5286,6 @@ isNotificationSent(const std::string &event_id)
return instance_->isNotificationSent(event_id);
}
-//! Add all notifications containing a user mention to the db.
-void
-saveTimelineMentions(const mtx::responses::Notifications &res)
-{
- instance_->saveTimelineMentions(res);
-}
-
//! Remove old unused data.
void
deleteOldMessages()
diff --git a/src/Cache.h b/src/Cache.h
index 7ea659ec..bb779866 100644
--- a/src/Cache.h
+++ b/src/Cache.h
@@ -118,9 +118,6 @@ setCurrentFormat();
bool
runMigrations();
-QMap<QString, mtx::responses::Notifications>
-getTimelineMentions();
-
//! Retrieve all the user ids from a room.
std::vector<std::string>
roomMembers(const std::string &room_id);
@@ -178,10 +175,6 @@ removeReadNotification(const std::string &event_id);
bool
isNotificationSent(const std::string &event_id);
-//! Add all notifications containing a user mention to the db.
-void
-saveTimelineMentions(const mtx::responses::Notifications &res);
-
//! Remove old unused data.
void
deleteOldMessages();
diff --git a/src/Cache_p.h b/src/Cache_p.h
index 0c75acdb..1694adb7 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -138,7 +138,6 @@ public:
bool runMigrations();
std::vector<QString> roomIds();
- QMap<QString, mtx::responses::Notifications> getTimelineMentions();
//! Retrieve all the user ids from a room.
std::vector<std::string> roomMembers(const std::string &room_id);
@@ -179,9 +178,6 @@ public:
//! Check if we have sent a desktop notification for the given event id.
bool isNotificationSent(const std::string &event_id);
- //! Add all notifications containing a user mention to the db.
- void saveTimelineMentions(const mtx::responses::Notifications &res);
-
//! retrieve events in timeline and related functions
struct Messages
{
@@ -318,7 +314,6 @@ public:
signals:
void newReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
void roomReadStatus(const std::map<QString, bool> &status);
- void removeNotification(const QString &room_id, const QString &event_id);
void userKeysUpdate(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery);
void userKeysUpdateFinalize(const std::string &user_id);
void verificationStatusChanged(const std::string &userid);
@@ -335,15 +330,6 @@ private:
lmdb::dbi &membersdb,
const mtx::responses::InvitedRoom &room);
- //! Add a notification containing a user mention to the db.
- void saveTimelineMentions(lmdb::txn &txn,
- const std::string &room_id,
- const QList<mtx::responses::Notification> &res);
-
- //! Get timeline items that a user was mentions in for a given room
- mtx::responses::Notifications
- getTimelineMentionsForRoom(lmdb::txn &txn, const std::string &room_id);
-
QString getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
QString getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
QString getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
@@ -642,11 +628,6 @@ private:
return lmdb::dbi::open(txn, std::string(room_id + "/members").c_str(), MDB_CREATE);
}
- lmdb::dbi getMentionsDb(lmdb::txn &txn, const std::string &room_id)
- {
- return lmdb::dbi::open(txn, std::string(room_id + "/mentions").c_str(), MDB_CREATE);
- }
-
lmdb::dbi getUserKeysDb(lmdb::txn &txn) { return lmdb::dbi::open(txn, "user_key", MDB_CREATE); }
lmdb::dbi getVerificationDb(lmdb::txn &txn)
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index d6b3d292..1f38d763 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -136,18 +136,6 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QObject *parent)
Qt::QueuedConnection);
connect(this, &ChatPage::leftRoom, this, &ChatPage::removeRoom);
connect(this, &ChatPage::changeToRoom, this, &ChatPage::changeRoom, Qt::QueuedConnection);
- connect(this, &ChatPage::notificationsRetrieved, this, &ChatPage::sendNotifications);
- connect(this,
- &ChatPage::highlightedNotifsRetrieved,
- this,
- [](const mtx::responses::Notifications ¬if) {
- try {
- cache::saveTimelineMentions(notif);
- } catch (const lmdb::error &e) {
- nhlog::db()->error("failed to save mentions: {}", e.what());
- }
- });
-
connect(notificationsManager,
&NotificationsManager::notificationClicked,
this,
@@ -208,18 +196,140 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QObject *parent)
// No need to check amounts for this section, as this function internally checks for
// duplicates.
if (notificationCount && userSettings_->hasNotifications())
- http::client()->notifications(
- 5,
- "",
- "",
- [this](const mtx::responses::Notifications &res, mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->warn("failed to retrieve notifications: {}", err);
- return;
- }
+ for (const auto &e : sync.account_data.events) {
+ if (auto newRules =
+ std::get_if<mtx::events::AccountDataEvent<mtx::pushrules::GlobalRuleset>>(&e))
+ pushrules =
+ std::make_unique<mtx::pushrules::PushRuleEvaluator>(newRules->content.global);
+ }
+ if (!pushrules) {
+ auto eventInDb = cache::client()->getAccountData(mtx::events::EventType::PushRules);
+ if (eventInDb) {
+ if (auto newRules =
+ std::get_if<mtx::events::AccountDataEvent<mtx::pushrules::GlobalRuleset>>(
+ &*eventInDb))
+ pushrules =
+ std::make_unique<mtx::pushrules::PushRuleEvaluator>(newRules->content.global);
+ }
+ }
+ if (pushrules) {
+ const auto local_user = utils::localUser().toStdString();
- emit notificationsRetrieved(std::move(res));
- });
+ for (const auto &[room_id, room] : sync.rooms.join) {
+ // clear old notifications
+ for (const auto &e : room.ephemeral.events) {
+ if (auto receiptsEv =
+ std::get_if<mtx::events::EphemeralEvent<mtx::events::ephemeral::Receipt>>(
+ &e)) {
+ std::vector<QString> receipts;
+
+ for (const auto &[event_id, userReceipts] : receiptsEv->content.receipts) {
+ if (auto r = userReceipts.find(mtx::events::ephemeral::Receipt::Read);
+ r != userReceipts.end()) {
+ for (const auto &[user_id, receipt] : r->second.users) {
+ (void)receipt;
+
+ if (user_id == local_user) {
+ receipts.push_back(QString::fromStdString(event_id));
+ break;
+ }
+ }
+ }
+ if (auto r =
+ userReceipts.find(mtx::events::ephemeral::Receipt::ReadPrivate);
+ r != userReceipts.end()) {
+ for (const auto &[user_id, receipt] : r->second.users) {
+ (void)receipt;
+
+ if (user_id == local_user) {
+ receipts.push_back(QString::fromStdString(event_id));
+ break;
+ }
+ }
+ }
+ }
+ if (!receipts.empty())
+ notificationsManager->removeNotifications(
+ QString::fromStdString(room_id), receipts);
+ }
+ }
+
+ // calculate new notifications
+ if (!room.timeline.events.empty() &&
+ (room.unread_notifications.notification_count ||
+ room.unread_notifications.highlight_count)) {
+ auto roomModel =
+ view_manager_->rooms()->getRoomById(QString::fromStdString(room_id));
+
+ if (!roomModel) {
+ continue;
+ }
+
+ auto currentReadMarker =
+ cache::getEventIndex(room_id, cache::client()->getFullyReadEventId(room_id));
+
+ auto ctx = roomModel->pushrulesRoomContext();
+ for (const auto &event : room.timeline.events) {
+ mtx::events::collections::TimelineEvent te{event};
+ std::visit([room_id = room_id](auto &event_) { event_.room_id = room_id; },
+ te.data);
+
+ if (auto encryptedEvent =
+ std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
+ &event)) {
+ MegolmSessionIndex index(room_id, encryptedEvent->content);
+
+ auto result = olm::decryptEvent(index, *encryptedEvent);
+ if (result.event)
+ te.data = result.event.value();
+ }
+
+ auto actions = pushrules->evaluate(te, ctx);
+ if (std::find(actions.begin(),
+ actions.end(),
+ mtx::pushrules::actions::Action{
+ mtx::pushrules::actions::notify{}}) != actions.end()) {
+ auto event_id = mtx::accessors::event_id(event);
+
+ // skip already read events
+ if (currentReadMarker &&
+ currentReadMarker > cache::getEventIndex(room_id, event_id))
+ continue;
+
+ if (!cache::isNotificationSent(event_id)) {
+ // We should only send one notification per event.
+ cache::markSentNotification(event_id);
+
+ // Don't send a notification when the current room is opened.
+ if (isRoomActive(roomModel->roomId()))
+ continue;
+
+ if (userSettings_->hasDesktopNotifications()) {
+ auto info = cache::singleRoomInfo(room_id);
+
+ AvatarProvider::resolve(
+ roomModel->roomAvatarUrl(),
+ 96,
+ this,
+ [this, te, room_id = room_id, actions](QPixmap image) {
+ notificationsManager->postNotification(
+ mtx::responses::Notification{
+ .actions = actions,
+ .event = te.data,
+ .read = false,
+ .profile_tag = "",
+ .room_id = room_id,
+ .ts = 0,
+ },
+ image.toImage());
+ });
+ }
+ }
+ }
+ }
+ }
+ }
+ }
});
connect(
@@ -394,12 +504,6 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
&Cache::newReadReceipts,
view_manager_,
&TimelineViewManager::updateReadReceipts);
-
- connect(cache::client(),
- &Cache::removeNotification,
- notificationsManager,
- &NotificationsManager::removeNotification);
-
} catch (const lmdb::error &e) {
nhlog::db()->critical("failure during boot: {}", e.what());
emit dropToLoginPageCb(tr("Failed to open database, logging out!"));
@@ -415,7 +519,6 @@ ChatPage::loadStateFromCache()
olm::client()->load(cache::restoreOlmAccount(), cache::client()->pickleSecret());
emit initializeEmptyViews();
- emit initializeMentions(cache::getTimelineMentions());
cache::calculateRoomReadStatus();
@@ -465,46 +568,6 @@ ChatPage::removeRoom(const QString &room_id)
}
void
-ChatPage::sendNotifications(const mtx::responses::Notifications &res)
-{
- for (const auto &item : res.notifications) {
- const auto event_id = mtx::accessors::event_id(item.event);
-
- try {
- if (item.read) {
- cache::removeReadNotification(event_id);
- continue;
- }
-
- if (!cache::isNotificationSent(event_id)) {
- const auto room_id = QString::fromStdString(item.room_id);
-
- // We should only send one notification per event.
- cache::markSentNotification(event_id);
-
- // Don't send a notification when the current room is opened.
- if (isRoomActive(room_id))
- continue;
-
- if (userSettings_->hasDesktopNotifications()) {
- auto info = cache::singleRoomInfo(item.room_id);
-
- AvatarProvider::resolve(QString::fromStdString(info.avatar_url),
- 96,
- this,
- [this, item](QPixmap image) {
- notificationsManager->postNotification(
- item, image.toImage());
- });
- }
- }
- } catch (const lmdb::error &e) {
- nhlog::db()->warn("error while sending notification: {}", e.what());
- }
- }
-}
-
-void
ChatPage::tryInitialSync()
{
nhlog::crypto()->info("ed25519 : {}", olm::client()->identity_keys().ed25519);
@@ -596,7 +659,6 @@ ChatPage::startInitialSync()
olm::handle_to_device_messages(res.to_device.events);
emit initializeViews(std::move(res));
- emit initializeMentions(cache::getTimelineMentions());
cache::calculateRoomReadStatus();
} catch (const lmdb::error &e) {
diff --git a/src/ChatPage.h b/src/ChatPage.h
index 73b7efcc..1bb25dc2 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -14,6 +14,7 @@
#include <mtx/events.hpp>
#include <mtx/events/encrypted.hpp>
#include <mtx/events/member.hpp>
+#include <mtx/events/policy_rules.hpp>
#include <mtx/events/presence.hpp>
#include <mtx/secret_storage.hpp>
@@ -108,9 +109,6 @@ signals:
void connectionLost();
void connectionRestored();
- void notificationsRetrieved(const mtx::responses::Notifications &);
- void highlightedNotifsRetrieved(const mtx::responses::Notifications &, const QPoint widgetPos);
-
void contentLoaded();
void closing();
void changeWindowTitle(const int);
@@ -135,7 +133,6 @@ signals:
void initializeViews(const mtx::responses::Sync &rooms);
void initializeEmptyViews();
- void initializeMentions(const QMap<QString, mtx::responses::Notifications> ¬ifs);
void syncUI(const mtx::responses::Sync &sync);
void dropToLoginPageCb(const QString &msg);
@@ -206,9 +203,6 @@ private:
template<class Collection>
Memberships getMemberships(const std::vector<Collection> &events) const;
- //! Send desktop notification for the received messages.
- void sendNotifications(const mtx::responses::Notifications &);
-
template<typename T>
void connectCallMessage();
@@ -222,6 +216,8 @@ private:
NotificationsManager *notificationsManager;
CallManager *callManager_;
+
+ std::unique_ptr<mtx::pushrules::PushRuleEvaluator> pushrules;
};
template<class Collection>
diff --git a/src/notifications/Manager.cpp b/src/notifications/Manager.cpp
index 501bfb3f..6033cc6d 100644
--- a/src/notifications/Manager.cpp
+++ b/src/notifications/Manager.cpp
@@ -7,6 +7,7 @@
#include "Cache.h"
#include "EventAccessors.h"
+#include "Logging.h"
#include "Utils.h"
QString
@@ -33,3 +34,24 @@ NotificationsManager::getMessageTemplate(const mtx::responses::Notification ¬
return QStringLiteral("%1: %2").arg(sender);
}
}
+
+void
+NotificationsManager::removeNotifications(const QString &roomId,
+ const std::vector<QString> &eventIds)
+{
+ std::string room_id = roomId.toStdString();
+
+ std::uint64_t markerPos = 0;
+ for (const auto &e : eventIds) {
+ markerPos = std::max(markerPos, cache::getEventIndex(room_id, e.toStdString()).value_or(0));
+ }
+
+ for (const auto &[roomId, eventId] : this->notificationIds) {
+ if (roomId != roomId)
+ continue;
+ auto idx = cache::getEventIndex(room_id, eventId.toStdString());
+ if (!idx || markerPos >= idx) {
+ removeNotification(roomId, eventId);
+ }
+ }
+}
diff --git a/src/notifications/Manager.h b/src/notifications/Manager.h
index f3b1fe30..05f06dcf 100644
--- a/src/notifications/Manager.h
+++ b/src/notifications/Manager.h
@@ -36,6 +36,8 @@ public:
void postNotification(const mtx::responses::Notification ¬ification, const QImage &icon);
+ void removeNotification(const QString &roomId, const QString &eventId);
+
signals:
void notificationClicked(const QString roomId, const QString eventId);
void sendNotificationReply(const QString roomId, const QString eventId, const QString body);
@@ -46,7 +48,7 @@ signals:
const QImage &icon);
public slots:
- void removeNotification(const QString &roomId, const QString &eventId);
+ void removeNotifications(const QString &roomId, const std::vector<QString> &eventId);
#if defined(NHEKO_DBUS_SYS)
public:
@@ -62,9 +64,6 @@ private:
const QImage &icon);
void closeNotification(uint id);
- // notification ID to (room ID, event ID)
- QMap<uint, roomEventId> notificationIds;
-
const bool hasMarkup_;
const bool hasImages_;
#endif
@@ -96,6 +95,10 @@ private slots:
private:
QString getMessageTemplate(const mtx::responses::Notification ¬ification);
+
+ // notification ID to (room ID, event ID)
+ // Only populated on Linux atm
+ QMap<uint, roomEventId> notificationIds;
};
#if defined(NHEKO_DBUS_SYS)
diff --git a/src/timeline/Permissions.h b/src/timeline/Permissions.h
index aa10999d..1b3f55e4 100644
--- a/src/timeline/Permissions.h
+++ b/src/timeline/Permissions.h
@@ -34,6 +34,8 @@ public:
void invalidate();
+ const mtx::events::state::PowerLevels &powerlevelEvent() const { return pl; };
+
private:
QString roomId_;
mtx::events::state::PowerLevels pl;
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 0eec5cae..0e726bde 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -2921,6 +2921,17 @@ TimelineModel::directChatOtherUserId() const
return {};
}
+mtx::pushrules::PushRuleEvaluator::RoomContext
+TimelineModel::pushrulesRoomContext() const
+{
+ return mtx::pushrules::PushRuleEvaluator::RoomContext{
+ .user_display_name =
+ cache::displayName(room_id_.toStdString(), http::client()->user_id().to_string()),
+ .member_count = cache::client()->memberCount(room_id_.toStdString()),
+ .power_levels = permissions_.powerlevelEvent(),
+ };
+}
+
RoomSummary *
TimelineModel::parentSpace()
{
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index 8e191556..a4904f4f 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -336,6 +336,8 @@ public:
bool isDirect() const { return roomMemberCount() <= 2; }
QString directChatOtherUserId() const;
+ mtx::pushrules::PushRuleEvaluator::RoomContext pushrulesRoomContext() const;
+
std::optional<mtx::events::collections::TimelineEvents> eventById(const QString &id)
{
auto e = events.get(id.toStdString(), "");
|