matrix_sdk_ffi/
notification_settings.rs

1use std::sync::{Arc, RwLock};
2
3use matrix_sdk::{
4    event_handler::EventHandlerHandle,
5    notification_settings::{
6        NotificationSettings as SdkNotificationSettings,
7        RoomNotificationMode as SdkRoomNotificationMode,
8    },
9    ruma::events::push_rules::PushRulesEvent,
10    Client as MatrixClient,
11};
12use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
13use ruma::{
14    events::push_rules::PushRulesEventContent,
15    push::{
16        Action as SdkAction, ComparisonOperator as SdkComparisonOperator, PredefinedOverrideRuleId,
17        PredefinedUnderrideRuleId, PushCondition as SdkPushCondition, RoomMemberCountIs,
18        RuleKind as SdkRuleKind, ScalarJsonValue as SdkJsonValue, Tweak as SdkTweak,
19    },
20    Int, RoomId, UInt,
21};
22use tokio::sync::RwLock as AsyncRwLock;
23
24use crate::error::{ClientError, NotificationSettingsError};
25
26#[derive(Clone, Default, uniffi::Enum)]
27pub enum ComparisonOperator {
28    /// Equals
29    #[default]
30    Eq,
31
32    /// Less than
33    Lt,
34
35    /// Greater than
36    Gt,
37
38    /// Greater or equal
39    Ge,
40
41    /// Less or equal
42    Le,
43}
44
45impl From<SdkComparisonOperator> for ComparisonOperator {
46    fn from(value: SdkComparisonOperator) -> Self {
47        match value {
48            SdkComparisonOperator::Eq => Self::Eq,
49            SdkComparisonOperator::Lt => Self::Lt,
50            SdkComparisonOperator::Gt => Self::Gt,
51            SdkComparisonOperator::Ge => Self::Ge,
52            SdkComparisonOperator::Le => Self::Le,
53        }
54    }
55}
56
57impl From<ComparisonOperator> for SdkComparisonOperator {
58    fn from(value: ComparisonOperator) -> Self {
59        match value {
60            ComparisonOperator::Eq => Self::Eq,
61            ComparisonOperator::Lt => Self::Lt,
62            ComparisonOperator::Gt => Self::Gt,
63            ComparisonOperator::Ge => Self::Ge,
64            ComparisonOperator::Le => Self::Le,
65        }
66    }
67}
68
69#[derive(Debug, Clone, Default, uniffi::Enum)]
70pub enum JsonValue {
71    /// Represents a `null` value.
72    #[default]
73    Null,
74
75    /// Represents a boolean.
76    Bool { value: bool },
77
78    /// Represents an integer.
79    Integer { value: i64 },
80
81    /// Represents a string.
82    String { value: String },
83}
84
85impl From<SdkJsonValue> for JsonValue {
86    fn from(value: SdkJsonValue) -> Self {
87        match value {
88            SdkJsonValue::Null => Self::Null,
89            SdkJsonValue::Bool(b) => Self::Bool { value: b },
90            SdkJsonValue::Integer(i) => Self::Integer { value: i.into() },
91            SdkJsonValue::String(s) => Self::String { value: s },
92        }
93    }
94}
95
96impl From<JsonValue> for SdkJsonValue {
97    fn from(value: JsonValue) -> Self {
98        match value {
99            JsonValue::Null => Self::Null,
100            JsonValue::Bool { value } => Self::Bool(value),
101            JsonValue::Integer { value } => Self::Integer(Int::new(value).unwrap_or_default()),
102            JsonValue::String { value } => Self::String(value),
103        }
104    }
105}
106
107#[derive(Clone, uniffi::Enum)]
108pub enum PushCondition {
109    /// A glob pattern match on a field of the event.
110    EventMatch {
111        /// The [dot-separated path] of the property of the event to match.
112        ///
113        /// [dot-separated path]: https://spec.matrix.org/latest/appendices/#dot-separated-property-paths
114        key: String,
115
116        /// The glob-style pattern to match against.
117        ///
118        /// Patterns with no special glob characters should be treated as having
119        /// asterisks prepended and appended when testing the condition.
120        pattern: String,
121    },
122
123    /// Matches unencrypted messages where `content.body` contains the owner's
124    /// display name in that room.
125    ContainsDisplayName,
126
127    /// Matches the current number of members in the room.
128    RoomMemberCount { prefix: ComparisonOperator, count: u64 },
129
130    /// Takes into account the current power levels in the room, ensuring the
131    /// sender of the event has high enough power to trigger the
132    /// notification.
133    SenderNotificationPermission {
134        /// The field in the power level event the user needs a minimum power
135        /// level for.
136        ///
137        /// Fields must be specified under the `notifications` property in the
138        /// power level event's `content`.
139        key: String,
140    },
141
142    /// Exact value match on a property of the event.
143    EventPropertyIs {
144        /// The [dot-separated path] of the property of the event to match.
145        ///
146        /// [dot-separated path]: https://spec.matrix.org/latest/appendices/#dot-separated-property-paths
147        key: String,
148
149        /// The value to match against.
150        value: JsonValue,
151    },
152
153    /// Exact value match on a value in an array property of the event.
154    EventPropertyContains {
155        /// The [dot-separated path] of the property of the event to match.
156        ///
157        /// [dot-separated path]: https://spec.matrix.org/latest/appendices/#dot-separated-property-paths
158        key: String,
159
160        /// The value to match against.
161        value: JsonValue,
162    },
163}
164
165impl TryFrom<SdkPushCondition> for PushCondition {
166    type Error = String;
167
168    fn try_from(value: SdkPushCondition) -> Result<Self, Self::Error> {
169        Ok(match value {
170            SdkPushCondition::EventMatch { key, pattern } => Self::EventMatch { key, pattern },
171            #[allow(deprecated)]
172            SdkPushCondition::ContainsDisplayName => Self::ContainsDisplayName,
173            SdkPushCondition::RoomMemberCount { is } => {
174                Self::RoomMemberCount { prefix: is.prefix.into(), count: is.count.into() }
175            }
176            SdkPushCondition::SenderNotificationPermission { key } => {
177                Self::SenderNotificationPermission { key: key.to_string() }
178            }
179            SdkPushCondition::EventPropertyIs { key, value } => {
180                Self::EventPropertyIs { key, value: value.into() }
181            }
182            SdkPushCondition::EventPropertyContains { key, value } => {
183                Self::EventPropertyContains { key, value: value.into() }
184            }
185            _ => return Err("Unsupported condition type".to_owned()),
186        })
187    }
188}
189
190impl From<PushCondition> for SdkPushCondition {
191    fn from(value: PushCondition) -> Self {
192        match value {
193            PushCondition::EventMatch { key, pattern } => Self::EventMatch { key, pattern },
194            #[allow(deprecated)]
195            PushCondition::ContainsDisplayName => Self::ContainsDisplayName,
196            PushCondition::RoomMemberCount { prefix, count } => Self::RoomMemberCount {
197                is: RoomMemberCountIs {
198                    prefix: prefix.into(),
199                    count: UInt::new(count).unwrap_or_default(),
200                },
201            },
202            PushCondition::SenderNotificationPermission { key } => {
203                Self::SenderNotificationPermission { key: key.into() }
204            }
205            PushCondition::EventPropertyIs { key, value } => {
206                Self::EventPropertyIs { key, value: value.into() }
207            }
208            PushCondition::EventPropertyContains { key, value } => {
209                Self::EventPropertyContains { key, value: value.into() }
210            }
211        }
212    }
213}
214
215#[derive(Clone, uniffi::Enum)]
216pub enum RuleKind {
217    /// User-configured rules that override all other kinds.
218    Override,
219
220    /// Lowest priority user-defined rules.
221    Underride,
222
223    /// Sender-specific rules.
224    Sender,
225
226    /// Room-specific rules.
227    Room,
228
229    /// Content-specific rules.
230    Content,
231
232    Custom {
233        value: String,
234    },
235}
236
237impl From<SdkRuleKind> for RuleKind {
238    fn from(value: SdkRuleKind) -> Self {
239        match value {
240            SdkRuleKind::Override => Self::Override,
241            SdkRuleKind::Underride => Self::Underride,
242            SdkRuleKind::Sender => Self::Sender,
243            SdkRuleKind::Room => Self::Room,
244            SdkRuleKind::Content => Self::Content,
245            SdkRuleKind::_Custom(_) => Self::Custom { value: value.as_str().to_owned() },
246            _ => Self::Custom { value: value.to_string() },
247        }
248    }
249}
250
251impl From<RuleKind> for SdkRuleKind {
252    fn from(value: RuleKind) -> Self {
253        match value {
254            RuleKind::Override => Self::Override,
255            RuleKind::Underride => Self::Underride,
256            RuleKind::Sender => Self::Sender,
257            RuleKind::Room => Self::Room,
258            RuleKind::Content => Self::Content,
259            RuleKind::Custom { value } => SdkRuleKind::from(value),
260        }
261    }
262}
263
264#[derive(Clone, uniffi::Enum)]
265/// Enum representing the push notification tweaks for a rule.
266pub enum Tweak {
267    /// A string representing the sound to be played when this notification
268    /// arrives.
269    ///
270    /// A value of "default" means to play a default sound. A device may choose
271    /// to alert the user by some other means if appropriate, eg. vibration.
272    Sound { value: String },
273
274    /// A boolean representing whether or not this message should be highlighted
275    /// in the UI.
276    Highlight { value: bool },
277
278    /// A custom tweak
279    Custom {
280        /// The name of the custom tweak (`set_tweak` field)
281        name: String,
282
283        /// The value of the custom tweak as an encoded JSON string
284        value: String,
285    },
286}
287
288impl TryFrom<SdkTweak> for Tweak {
289    type Error = String;
290
291    fn try_from(value: SdkTweak) -> Result<Self, Self::Error> {
292        Ok(match value {
293            SdkTweak::Sound(sound) => Self::Sound { value: sound },
294            SdkTweak::Highlight(highlight) => Self::Highlight { value: highlight },
295            SdkTweak::Custom { name, value } => {
296                let json_string = serde_json::to_string(&value)
297                    .map_err(|e| format!("Failed to serialize custom tweak value: {e}"))?;
298
299                Self::Custom { name, value: json_string }
300            }
301            _ => return Err("Unsupported tweak type".to_owned()),
302        })
303    }
304}
305
306impl TryFrom<Tweak> for SdkTweak {
307    type Error = String;
308
309    fn try_from(value: Tweak) -> Result<Self, Self::Error> {
310        Ok(match value {
311            Tweak::Sound { value } => Self::Sound(value),
312            Tweak::Highlight { value } => Self::Highlight(value),
313            Tweak::Custom { name, value } => {
314                let json_value: serde_json::Value = serde_json::from_str(&value)
315                    .map_err(|e| format!("Failed to deserialize custom tweak value: {e}"))?;
316                let value = serde_json::from_value(json_value)
317                    .map_err(|e| format!("Failed to convert JSON value: {e}"))?;
318
319                Self::Custom { name, value }
320            }
321        })
322    }
323}
324
325#[derive(Clone, uniffi::Enum)]
326/// Enum representing the push notification actions for a rule.
327pub enum Action {
328    /// Causes matching events to generate a notification.
329    Notify,
330    /// Sets an entry in the 'tweaks' dictionary sent to the push gateway.
331    SetTweak { value: Tweak },
332}
333
334impl TryFrom<SdkAction> for Action {
335    type Error = String;
336
337    fn try_from(value: SdkAction) -> Result<Self, Self::Error> {
338        Ok(match value {
339            SdkAction::Notify => Self::Notify,
340            SdkAction::SetTweak(tweak) => Self::SetTweak {
341                value: tweak.try_into().map_err(|e| format!("Failed to convert tweak: {e}"))?,
342            },
343            _ => return Err("Unsupported action type".to_owned()),
344        })
345    }
346}
347
348impl TryFrom<Action> for SdkAction {
349    type Error = String;
350
351    fn try_from(value: Action) -> Result<Self, Self::Error> {
352        Ok(match value {
353            Action::Notify => Self::Notify,
354            Action::SetTweak { value } => Self::SetTweak(
355                value.try_into().map_err(|e| format!("Failed to convert tweak: {e}"))?,
356            ),
357        })
358    }
359}
360
361/// Enum representing the push notification modes for a room.
362#[derive(Clone, uniffi::Enum)]
363pub enum RoomNotificationMode {
364    /// Receive notifications for all messages.
365    AllMessages,
366    /// Receive notifications for mentions and keywords only.
367    MentionsAndKeywordsOnly,
368    /// Do not receive any notifications.
369    Mute,
370}
371
372impl From<SdkRoomNotificationMode> for RoomNotificationMode {
373    fn from(value: SdkRoomNotificationMode) -> Self {
374        match value {
375            SdkRoomNotificationMode::AllMessages => Self::AllMessages,
376            SdkRoomNotificationMode::MentionsAndKeywordsOnly => Self::MentionsAndKeywordsOnly,
377            SdkRoomNotificationMode::Mute => Self::Mute,
378        }
379    }
380}
381
382impl From<RoomNotificationMode> for SdkRoomNotificationMode {
383    fn from(value: RoomNotificationMode) -> Self {
384        match value {
385            RoomNotificationMode::AllMessages => Self::AllMessages,
386            RoomNotificationMode::MentionsAndKeywordsOnly => Self::MentionsAndKeywordsOnly,
387            RoomNotificationMode::Mute => Self::Mute,
388        }
389    }
390}
391
392/// Delegate to notify of changes in push rules
393#[matrix_sdk_ffi_macros::export(callback_interface)]
394pub trait NotificationSettingsDelegate: SyncOutsideWasm + SendOutsideWasm {
395    fn settings_did_change(&self);
396}
397
398/// `RoomNotificationSettings` represents the current settings for a `Room`
399#[derive(Clone, uniffi::Record)]
400pub struct RoomNotificationSettings {
401    /// The room notification mode
402    mode: RoomNotificationMode,
403    /// Whether the mode is the default one
404    is_default: bool,
405}
406
407impl RoomNotificationSettings {
408    fn new(mode: RoomNotificationMode, is_default: bool) -> Self {
409        RoomNotificationSettings { mode, is_default }
410    }
411}
412
413#[derive(Clone, uniffi::Object)]
414pub struct NotificationSettings {
415    sdk_client: MatrixClient,
416    sdk_notification_settings: Arc<AsyncRwLock<SdkNotificationSettings>>,
417    pushrules_event_handler: Arc<RwLock<Option<EventHandlerHandle>>>,
418}
419
420impl NotificationSettings {
421    pub(crate) fn new(
422        sdk_client: MatrixClient,
423        sdk_notification_settings: SdkNotificationSettings,
424    ) -> Self {
425        Self {
426            sdk_client,
427            sdk_notification_settings: Arc::new(AsyncRwLock::new(sdk_notification_settings)),
428            pushrules_event_handler: Arc::new(RwLock::new(None)),
429        }
430    }
431}
432
433impl Drop for NotificationSettings {
434    fn drop(&mut self) {
435        // Remove the event handler on the sdk_client.
436        if let Some(event_handler) = self.pushrules_event_handler.read().unwrap().as_ref() {
437            self.sdk_client.remove_event_handler(event_handler.clone());
438        }
439    }
440}
441
442#[matrix_sdk_ffi_macros::export]
443impl NotificationSettings {
444    pub fn set_delegate(&self, delegate: Option<Box<dyn NotificationSettingsDelegate>>) {
445        if let Some(delegate) = delegate {
446            let delegate: Arc<dyn NotificationSettingsDelegate> = Arc::from(delegate);
447
448            // Add an event handler to listen to `PushRulesEvent`
449            let event_handler =
450                self.sdk_client.add_event_handler(move |_: PushRulesEvent| async move {
451                    delegate.settings_did_change();
452                });
453
454            *self.pushrules_event_handler.write().unwrap() = Some(event_handler);
455        } else {
456            // Remove the event handler if there is no delegate
457            let event_handler = &mut *self.pushrules_event_handler.write().unwrap();
458            if let Some(event_handler) = event_handler {
459                self.sdk_client.remove_event_handler(event_handler.clone());
460            }
461            *event_handler = None;
462        }
463    }
464
465    /// Get the notification settings for a room.
466    ///
467    /// # Arguments
468    ///
469    /// * `room_id` - the room ID
470    /// * `is_encrypted` - whether the room is encrypted
471    /// * `is_one_to_one` - whether the room is a direct chat involving two
472    ///   people
473    pub async fn get_room_notification_settings(
474        &self,
475        room_id: String,
476        is_encrypted: bool,
477        is_one_to_one: bool,
478    ) -> Result<RoomNotificationSettings, NotificationSettingsError> {
479        let parsed_room_id = RoomId::parse(&room_id)
480            .map_err(|_e| NotificationSettingsError::InvalidRoomId { room_id })?;
481
482        let notification_settings = self.sdk_notification_settings.read().await;
483
484        // Get the current user defined mode for this room
485        if let Some(mode) =
486            notification_settings.get_user_defined_room_notification_mode(&parsed_room_id).await
487        {
488            return Ok(RoomNotificationSettings::new(mode.into(), false));
489        }
490
491        // If the user has not defined a notification mode, return the default one for
492        // this room
493        let mode = notification_settings
494            .get_default_room_notification_mode(is_encrypted.into(), is_one_to_one.into())
495            .await;
496
497        Ok(RoomNotificationSettings::new(mode.into(), true))
498    }
499
500    /// Set the notification mode for a room.
501    pub async fn set_room_notification_mode(
502        &self,
503        room_id: String,
504        mode: RoomNotificationMode,
505    ) -> Result<(), NotificationSettingsError> {
506        let parsed_room_id = RoomId::parse(&room_id)
507            .map_err(|_e| NotificationSettingsError::InvalidRoomId { room_id })?;
508
509        self.sdk_notification_settings
510            .read()
511            .await
512            .set_room_notification_mode(&parsed_room_id, mode.into())
513            .await?;
514
515        Ok(())
516    }
517
518    /// Get the user defined room notification mode
519    pub async fn get_user_defined_room_notification_mode(
520        &self,
521        room_id: String,
522    ) -> Result<Option<RoomNotificationMode>, NotificationSettingsError> {
523        let notification_settings = self.sdk_notification_settings.read().await;
524        let parsed_room_id = RoomId::parse(&room_id)
525            .map_err(|_e| NotificationSettingsError::InvalidRoomId { room_id })?;
526        // Get the current user defined mode for this room
527        if let Some(mode) =
528            notification_settings.get_user_defined_room_notification_mode(&parsed_room_id).await
529        {
530            Ok(Some(mode.into()))
531        } else {
532            Ok(None)
533        }
534    }
535
536    /// Get the default room notification mode
537    ///
538    /// The mode will depend on the associated `PushRule` based on whether the
539    /// room is encrypted or not, and on the number of members.
540    ///
541    /// # Arguments
542    ///
543    /// * `is_encrypted` - whether the room is encrypted
544    /// * `is_one_to_one` - whether the room is a direct chats involving two
545    ///   people
546    pub async fn get_default_room_notification_mode(
547        &self,
548        is_encrypted: bool,
549        is_one_to_one: bool,
550    ) -> RoomNotificationMode {
551        let notification_settings = self.sdk_notification_settings.read().await;
552        let mode = notification_settings
553            .get_default_room_notification_mode(is_encrypted.into(), is_one_to_one.into())
554            .await;
555        mode.into()
556    }
557
558    /// Set the default room notification mode
559    ///
560    /// # Arguments
561    ///
562    /// * `is_encrypted` - whether the mode is for encrypted rooms
563    /// * `is_one_to_one` - whether the mode is for direct chats involving two
564    ///   people
565    /// * `mode` - the new default mode
566    pub async fn set_default_room_notification_mode(
567        &self,
568        is_encrypted: bool,
569        is_one_to_one: bool,
570        mode: RoomNotificationMode,
571    ) -> Result<(), NotificationSettingsError> {
572        let notification_settings = self.sdk_notification_settings.read().await;
573        notification_settings
574            .set_default_room_notification_mode(
575                is_encrypted.into(),
576                is_one_to_one.into(),
577                mode.into(),
578            )
579            .await?;
580        Ok(())
581    }
582
583    /// Restore the default notification mode for a room
584    pub async fn restore_default_room_notification_mode(
585        &self,
586        room_id: String,
587    ) -> Result<(), NotificationSettingsError> {
588        let notification_settings = self.sdk_notification_settings.read().await;
589        let parsed_room_id = RoomId::parse(&room_id)
590            .map_err(|_e| NotificationSettingsError::InvalidRoomId { room_id })?;
591        notification_settings.delete_user_defined_room_rules(&parsed_room_id).await?;
592        Ok(())
593    }
594
595    /// Get all room IDs for which a user-defined rule exists.
596    pub async fn get_rooms_with_user_defined_rules(&self, enabled: Option<bool>) -> Vec<String> {
597        let notification_settings = self.sdk_notification_settings.read().await;
598        notification_settings.get_rooms_with_user_defined_rules(enabled).await
599    }
600
601    /// Get whether some enabled keyword rules exist.
602    pub async fn contains_keywords_rules(&self) -> bool {
603        let notification_settings = self.sdk_notification_settings.read().await;
604        notification_settings.contains_keyword_rules().await
605    }
606
607    /// Get whether room mentions are enabled.
608    pub async fn is_room_mention_enabled(&self) -> Result<bool, NotificationSettingsError> {
609        let notification_settings = self.sdk_notification_settings.read().await;
610        let enabled = notification_settings
611            .is_push_rule_enabled(SdkRuleKind::Override, PredefinedOverrideRuleId::IsRoomMention)
612            .await?;
613        Ok(enabled)
614    }
615
616    /// Set whether room mentions are enabled.
617    pub async fn set_room_mention_enabled(
618        &self,
619        enabled: bool,
620    ) -> Result<(), NotificationSettingsError> {
621        let notification_settings = self.sdk_notification_settings.read().await;
622        notification_settings
623            .set_push_rule_enabled(
624                SdkRuleKind::Override,
625                PredefinedOverrideRuleId::IsRoomMention,
626                enabled,
627            )
628            .await?;
629        Ok(())
630    }
631
632    /// Get whether user mentions are enabled.
633    pub async fn is_user_mention_enabled(&self) -> Result<bool, NotificationSettingsError> {
634        let notification_settings = self.sdk_notification_settings.read().await;
635        let enabled = notification_settings
636            .is_push_rule_enabled(SdkRuleKind::Override, PredefinedOverrideRuleId::IsUserMention)
637            .await?;
638        Ok(enabled)
639    }
640
641    /// Returns true if [MSC 4028 push rule][rule] is supported and enabled.
642    ///
643    /// [rule]: https://github.com/matrix-org/matrix-spec-proposals/blob/giomfo/push_encrypted_events/proposals/4028-push-all-encrypted-events-except-for-muted-rooms.md
644    pub async fn can_push_encrypted_event_to_device(&self) -> bool {
645        let notification_settings = self.sdk_notification_settings.read().await;
646        // Check stable identifier
647        if let Ok(enabled) = notification_settings
648            .is_push_rule_enabled(SdkRuleKind::Override, ".m.rule.encrypted_event")
649            .await
650        {
651            enabled
652        } else {
653            // Check unstable identifier
654            notification_settings
655                .is_push_rule_enabled(SdkRuleKind::Override, ".org.matrix.msc4028.encrypted_event")
656                .await
657                .unwrap_or(false)
658        }
659    }
660
661    /// Check whether [MSC 4028 push rule][rule] is enabled on the homeserver.
662    ///
663    /// [rule]: https://github.com/matrix-org/matrix-spec-proposals/blob/giomfo/push_encrypted_events/proposals/4028-push-all-encrypted-events-except-for-muted-rooms.md
664    pub async fn can_homeserver_push_encrypted_event_to_device(&self) -> bool {
665        self.sdk_client.can_homeserver_push_encrypted_event_to_device().await.unwrap()
666    }
667
668    /// Set whether user mentions are enabled.
669    pub async fn set_user_mention_enabled(
670        &self,
671        enabled: bool,
672    ) -> Result<(), NotificationSettingsError> {
673        let notification_settings = self.sdk_notification_settings.read().await;
674        notification_settings
675            .set_push_rule_enabled(
676                SdkRuleKind::Override,
677                PredefinedOverrideRuleId::IsUserMention,
678                enabled,
679            )
680            .await?;
681        Ok(())
682    }
683
684    /// Get whether the `.m.rule.call` push rule is enabled
685    pub async fn is_call_enabled(&self) -> Result<bool, NotificationSettingsError> {
686        let notification_settings = self.sdk_notification_settings.read().await;
687        let enabled = notification_settings
688            .is_push_rule_enabled(SdkRuleKind::Underride, PredefinedUnderrideRuleId::Call)
689            .await?;
690        Ok(enabled)
691    }
692
693    /// Set whether the `.m.rule.call` push rule is enabled
694    pub async fn set_call_enabled(&self, enabled: bool) -> Result<(), NotificationSettingsError> {
695        let notification_settings = self.sdk_notification_settings.read().await;
696        notification_settings
697            .set_push_rule_enabled(SdkRuleKind::Underride, PredefinedUnderrideRuleId::Call, enabled)
698            .await?;
699        Ok(())
700    }
701
702    /// Get whether the `.m.rule.invite_for_me` push rule is enabled
703    pub async fn is_invite_for_me_enabled(&self) -> Result<bool, NotificationSettingsError> {
704        let notification_settings = self.sdk_notification_settings.read().await;
705        let enabled = notification_settings
706            .is_push_rule_enabled(
707                SdkRuleKind::Override,
708                PredefinedOverrideRuleId::InviteForMe.as_str(),
709            )
710            .await?;
711        Ok(enabled)
712    }
713
714    /// Set whether the `.m.rule.invite_for_me` push rule is enabled
715    pub async fn set_invite_for_me_enabled(
716        &self,
717        enabled: bool,
718    ) -> Result<(), NotificationSettingsError> {
719        let notification_settings = self.sdk_notification_settings.read().await;
720        notification_settings
721            .set_push_rule_enabled(
722                SdkRuleKind::Override,
723                PredefinedOverrideRuleId::InviteForMe.as_str(),
724                enabled,
725            )
726            .await?;
727        Ok(())
728    }
729
730    /// Sets a custom push rule with the given actions and conditions.
731    pub async fn set_custom_push_rule(
732        &self,
733        rule_id: String,
734        rule_kind: RuleKind,
735        actions: Vec<Action>,
736        conditions: Vec<PushCondition>,
737    ) -> Result<(), NotificationSettingsError> {
738        let notification_settings = self.sdk_notification_settings.read().await;
739        let actions: Result<Vec<_>, _> =
740            actions.into_iter().map(|action| action.try_into()).collect();
741        let actions = actions.map_err(|e| NotificationSettingsError::Generic { msg: e })?;
742
743        notification_settings
744            .create_custom_conditional_push_rule(
745                rule_id,
746                rule_kind.into(),
747                actions,
748                conditions.into_iter().map(|condition| condition.into()).collect(),
749            )
750            .await?;
751        Ok(())
752    }
753
754    /// Unmute a room.
755    ///
756    /// # Arguments
757    ///
758    /// * `room_id` - the room to unmute
759    /// * `is_encrypted` - whether the room is encrypted
760    /// * `is_one_to_one` - whether the room is a direct chat involving two
761    ///   people
762    pub async fn unmute_room(
763        &self,
764        room_id: String,
765        is_encrypted: bool,
766        is_one_to_one: bool,
767    ) -> Result<(), NotificationSettingsError> {
768        let notification_settings = self.sdk_notification_settings.read().await;
769        let parsed_room_id = RoomId::parse(&room_id)
770            .map_err(|_e| NotificationSettingsError::InvalidRoomId { room_id })?;
771        notification_settings
772            .unmute_room(&parsed_room_id, is_encrypted.into(), is_one_to_one.into())
773            .await?;
774        Ok(())
775    }
776
777    /// Returns the raw push rules in JSON format.
778    pub async fn get_raw_push_rules(&self) -> Result<Option<String>, ClientError> {
779        let raw_push_rules =
780            self.sdk_client.account().account_data::<PushRulesEventContent>().await?;
781        Ok(raw_push_rules.map(|raw| serde_json::to_string(&raw)).transpose()?)
782    }
783}