Commit 96680d38cc31
tarball4 files changed+138-5
@@ -196,8 +196,11 @@ A room mute can suppress evaluated notification actions while structured
196196
mention metadata remains present. The classifier retains explicit user197197
mentions in muted rooms and marks notification delivery as suppressed. Room-wide198198
reasons require the relevant push conditions and sender power level. Missing199
-authorisation context remains unresolved for reevaluation. Keyword-only200
-notifications are outside the inbox. The classifier excludes the signed-in199
+authorisation context remains unresolved for reevaluation. An event that200
+notifies and highlights with no other reason, such as a loud keyword, enters the201
+inbox as `Keyword`. `add_notification_keyword` gives keyword rules a highlight202
+tweak, and upgrades an existing rule for the pattern that lacks one, since the203
+SDK's keyword rules only notify and play a sound. The classifier excludes the signed-in201204
user's own events even when they contain explicit mention metadata.202205
203206
The existing `NotificationClient` combines notification evaluation with@@ -988,6 +988,9 @@ impl Service {
988988
_ => {}989989
}990990
}991
+ if reasons.is_empty() && notify && highlight {992
+ reasons.push(MentionReason::Keyword);993
+ }991994
(reasons, !notify, unresolved)992995
}993996
@@ -17,6 +17,9 @@ pub enum MentionReason {
1717
Direct,1818
Reply,1919
Room,20
+ /// a highlighting push rule matched with no structured mention or reply, such as a21
+ /// notification keyword22
+ Keyword,2023
}2124
2225
#[derive(Clone, Debug, Eq, PartialEq)]@@ -11,21 +11,24 @@ use std::{
1111
1212
#[cfg(feature = "sqlite")]1313
use macltok::{EncryptedSessionStore, PasswordLogin, Restore, Session, SessionStore, StoreConfig};14
-use macltok::{Engine, EngineConfig, Event, MentionsPage};14
+use macltok::{Engine, EngineConfig, Event, MentionReason, MentionsPage};1515
use matrix_sdk::{1616
Client,1717
ruma::{OwnedRoomId, OwnedUserId, event_id, events::Mentions, room_id, user_id},1818
test_utils::mocks::{MatrixMockServer, RoomContextResponseTemplate},1919
};2020
use matrix_sdk_test::{ALICE, JoinedRoomBuilder, event_factory::EventFactory};21
-use ruma::push::{EventMatchConditionData, NewConditionalPushRule, PushCondition, Ruleset};21
+use ruma::push::{22
+ Action, EventMatchConditionData, HighlightTweakValue, NewConditionalPushRule,23
+ NewPatternedPushRule, PushCondition, Ruleset, SoundTweakValue, Tweak,24
+};2225
use serde_json::{Value, json};2326
#[cfg(feature = "sqlite")]2427
use support::receive_until;2528
use support::shut_down;2629
use wiremock::{2730
Mock, Request, Respond, ResponseTemplate,28
- matchers::{method, path, path_regex, query_param},31
+ matchers::{body_string_contains, method, path, path_regex, query_param},2932
};3033
3134
#[cfg(feature = "serde")]@@ -774,6 +777,127 @@ async fn event_context_uses_independent_direction_limits_and_saved_mention_conte
774777
server.server().verify().await;775778
}776779
780
+#[tokio::test]781
+async fn highlighting_keywords_are_mentions_and_quiet_ones_are_not() {782
+ let server = MatrixMockServer::new().await;783
+ let client = server.client_builder().build().await;784
+ let own_user = client.user_id().unwrap().to_owned();785
+ let room_id = room_id!("!keyword-policy:localhost");786
+ let sender = user_id!("@alice:localhost");787
+ let mut rules = Ruleset::server_default(&own_user);788
+ let notify = [789
+ Action::Notify,790
+ Action::SetTweak(Tweak::Sound(SoundTweakValue::Default)),791
+ ];792
+ let mut loud = notify.to_vec();793
+ loud.push(Action::SetTweak(Tweak::Highlight(HighlightTweakValue::Yes)));794
+ rules795
+ .content796
+ .insert(NewPatternedPushRule::new("deploy".to_owned(), "deploy".to_owned(), loud).into());797
+ rules.content.insert(798
+ NewPatternedPushRule::new("lunch".to_owned(), "lunch".to_owned(), notify.to_vec()).into(),799
+ );800
+ server801
+ .mock_sync()802
+ .ok_and_run(&client, {803
+ let own_user = own_user.clone();804
+ move |builder| {805
+ let factory = EventFactory::new().room(room_id);806
+ builder.add_global_account_data(factory.push_rules(rules));807
+ builder.add_joined_room(808
+ JoinedRoomBuilder::new(room_id).add_state_event(factory.member(&own_user)),809
+ );810
+ }811
+ })812
+ .await;813
+ let feed = SlidingFeed::new(814
+ room_id.to_owned(),815
+ vec![816
+ message(817
+ "$keyword",818
+ sender.as_str(),819
+ 1_000,820
+ "the deploy is done",821
+ None,822
+ ),823
+ message("$quiet", sender.as_str(), 2_000, "lunch anyone", None),824
+ message("$plain", sender.as_str(), 3_000, "nothing to see", None),825
+ message(826
+ "$keyword-direct",827
+ sender.as_str(),828
+ 4_000,829
+ "deploy now",830
+ Some(own_user.as_str()),831
+ ),832
+ message("$own-keyword", own_user.as_str(), 5_000, "my deploy", None),833
+ ],834
+ );835
+ feed.mount(&server).await;836
+837
+ let (engine, handle, mut events) = Engine::from_client(838
+ client,839
+ EngineConfig::default().with_initial_history_limit(0),840
+ )841
+ .await842
+ .unwrap();843
+ let running = tokio::spawn(engine.run());844
+845
+ let page = mentions_page(&mut events, room_id, |page| page.entries.len() >= 2).await;846
+ assert_eq!(847
+ entry_ids(&page),848
+ HashSet::from(["$keyword".to_owned(), "$keyword-direct".to_owned()])849
+ );850
+ let reasons: BTreeMap<_, _> = page851
+ .entries852
+ .iter()853
+ .map(|entry| (entry.key.event_id.to_string(), entry.reasons.clone()))854
+ .collect();855
+ assert_eq!(reasons["$keyword"], vec![MentionReason::Keyword]);856
+ assert_eq!(reasons["$keyword-direct"], vec![MentionReason::Direct]);857
+ assert_eq!(page.unread_count, 2);858
+ assert!(859
+ page.entries860
+ .iter()861
+ .all(|entry| !entry.notification_suppressed)862
+ );863
+864
+ shut_down(&handle, &mut events, running).await;865
+}866
+867
+#[tokio::test]868
+async fn adding_a_keyword_gives_its_rule_a_highlight() {869
+ let server = MatrixMockServer::new().await;870
+ let room_id = room_id!("!keyword-rule:localhost");871
+ let (client, _) = joined_client(&server, room_id).await;872
+ Mock::given(method("PUT"))873
+ .and(path("/_matrix/client/v3/pushrules/global/content/deploy"))874
+ .respond_with(ResponseTemplate::new(200).set_body_json(json!({})))875
+ .expect(1)876
+ .mount(server.server())877
+ .await;878
+ Mock::given(method("PUT"))879
+ .and(path(880
+ "/_matrix/client/v3/pushrules/global/content/deploy/actions",881
+ ))882
+ .and(body_string_contains("highlight"))883
+ .and(body_string_contains("notify"))884
+ .respond_with(ResponseTemplate::new(200).set_body_json(json!({})))885
+ .expect(1)886
+ .mount(server.server())887
+ .await;888
+ SlidingFeed::new(room_id.to_owned(), Vec::new())889
+ .mount(&server)890
+ .await;891
+892
+ let (engine, handle, mut events) = Engine::from_client(client, EngineConfig::default())893
+ .await894
+ .unwrap();895
+ let running = tokio::spawn(engine.run());896
+ handle.add_notification_keyword("deploy").await.unwrap();897
+ shut_down(&handle, &mut events, running).await;898
+ server.server().verify().await;899
+}900
+777901
#[cfg(feature = "sqlite")]778902
#[tokio::test]779903
async fn sqlite_mentions_survive_engine_shutdown_and_session_resume() {